原型链:每个对象内部存在一个内部链接,它会引用其他对象(称为原型对象),如果对象上没有找到需要的属性或者方法引用,引擎会去它链接的原型对象上查找,如果找不到再去链接的对象自身的原型对象上查找,以此类推,直到查找到Object的prototype原型对象,这一系列对象的链接就称作原型链。
在构造函数、实例和原型对象之间的原型链可以具体表现为:每个构造函数都有一个原型对象、原型对象上有一个属性指回构造函数、实例上有一个内部指针指向原型对象。当原型对象是另一个类型的实例时,就会和另一个组实例、原型对象、构造函数之间相关联,从而形成这些对象之间的链接。代码表现为下:
function Bar(){ console.log('Bar构造函数') } //new 关键字实际上是创建了一个新对象并原型对象关联。 let bar1 = new Bar(); // bar1 实例 Bar.prototype // 指向原型对象 Bar.prototype.constructor // 指向构造函数 bar1.__proto__ // 指向原型对象 console.log(bar1.__proto__ === Bar.prototype) //true //扩展 //new方法的简单实现 let newMethod = function (Parent, ...rest) { // 1.以构造器的prototype属性为原型,创建新对象; let child = Object.create(Parent.prototype); // 2.将this和调用参数传给构造器执行 let result = Parent.apply(child, rest); // 3.如果构造器没有手动返回对象,则返回第一步的对象 return typeof result === 'object' ? result : child; };
继承的实现方式:
1、原型链继承
通过改变默认原型对象为另一对象的实例实现
function Parent(){ console.log("父类") } function Child(){ console.log("子类") } Child.prototype = new Parent();
2、盗用构造函数继承
在子类构造函数中调用父类构造函数,利用apply,call实现。
function Parent(){ console.log("父类") } function Child(){ Parent.call(this); console.log("子类") } let child = new Child();
3、组合继承
利用原型链继承原型上的属性与方法,再盗用构造函数继承实例属性。
function Parent(name){ this.name = name; console.log("父类") } function Child(name, age){ //继承属性 Parent.call(this,name); this.age = age; console.log("子类") } //继承方法 Child.prototype = new Parent();
4、原型式继承
通过一个临时构造函数实现原型链继承。
function object(o){ function F(){}; F.prototype = o; return new F(); } //ES5增加了Object.create()方法以实现原型式继承 //ES6之后,新增setPrototypeOf、getPrototypeOf Object.setPrototypeOf(Bar.prototype, Foo.prototype);
5、寄生式继承
创建一个实现继承的函数、以某种方式增强对象,然后返回这个对象。
function createAnother(origin){ let clone = Object.create(origin) //以某种方式增强对象 clone.sayHi = function(){ console.log('hi'); } //返回这个对象 return clone; }
6、寄生组合继承
使用寄生式继承来继承父类原型,然后将返回的新对象赋值给子类原型。
function extendPrototype(Child, Parent){ //创建指向原型指向Parent的对象prototype let prototype = Object.create(Parent); //修复丢失的构造函数 prototype.constructor = Child; Child.prototype = prototype; }
以上是javaScrpit中的几种继承方式,然而除了构造函数继承外,涉及[[prototype]]的继承实际上都是对象之间的委托关联。javaScrpit中的继承不是像传统类那样执行复制操作。
委托是不同于类的设计模式,对象并不是按照父类到子类的关系垂直组织的,而是通过任意方向的委托关联并排组织的。
let Foo = { setName: function(Name) { this.name = Name; }, getName: function() { console.log( this.name ); } }; // 让Bar委托Foo let Bar = Object.create( Foo ); // Bar是存在一个原型对象指向Foo的对象。 Bar.SetNameAndAge = function(Name,Age) { this.setName( Name ); this.age = Age; }; Bar.getNameAndAge = function() { this.getName(); console.log( this.age ); }; // 让b1委托Bar let b1 = Object.create( Bar ); b1.SetNameAndAge('tom',3) // 让b2委托Bar let b2 = Object.create( Bar ); b2.SetNameAndAge('jerry',4) b1.getNameAndAge() b2.getNameAndAge()