ES5实现继承的六种方式 原型链 利用原型链让一个引用类型继承另一个引用类型的属性和方法。
function SuperType () { this .property = true ; } SuperType .prototype .getSuperValue = function ( ) { return this .property ; }; function SubType () { this .subProperty = false ; } SubType .prototype = new SuperType ();SubType .prototype .getSubValue = function ( ) { return this .subProperty ; }; var instance = new SubType ();console .log (instance);console .log (instance.getSuperValue ()); console .log (instance instanceof SubType ); console .log (instance instanceof SuperType ); console .log (instance instanceof Object ); console .log (SubType .prototype .isPrototypeOf (instance)); console .log (SuperType .prototype .isPrototypeOf (instance)); console .log (Object .prototype .isPrototypeOf (instance));
缺点:
来自原型对象的引用属性是所有实例共享的。
创建子类实例时,无法向父类构造函数传参。
举例如下:
function SuperType () { this .colors = ['red' , 'blue' , 'green' ]; } function SubType () {} SubType .prototype = new SuperType ();var instance1 = new SubType ();instance1.colors .push ('black' ); console .log (instance1.colors ); var instance2 = new SubType ();console .log (instance2.colors );
借用构造函数 在子类构造函数的内部通过call()以及apply()调用父类构造函数。
function SuperType (name) { this .name = name; this .colors = ['red' , 'blue' , 'green' ]; this .getName = function ( ) { return this .name ; } } function SubType (name) { SuperType .call (this , name); this .age = 20 ; } var instance1 = new SubType ('Tom' );instance1.colors .push ('black' ); console .log (instance1.name ); console .log (instance1.getName ()); console .log (instance1.age ); console .log (instance1.colors ); var instance2 = new SubType ('Peter' );console .log (instance2.name ); console .log (instance2.getName ()); console .log (instance2.age ); console .log (instance2.colors );
可以看到,借用构造函数实现继承,解决了原型链继承的两个问题,既可以在新建子类实例的时候给父类构造函数传递参数,也不会造成子类实例共享父类引用变量。
但是你注意到了吗,这里我们把父类方法也写在了SuperType()构造函数里面,可以像前面一样写在SuperType.prototype上吗?
答案是不可以,必须写在SuperType()构造函数里面。因为这里是通过调用SuperType.call(this)来实现继承的,并没有通过new生成一个父类实例,所以如果写在prototype上,子类是无法拿到的。
缺点:如果方法都在构造函数中定义,那么就无法复用函数。每次构建实例时都会在实例中保留方法函数,造成了内存的浪费,同时也无法实现同步更新,因为每个实例都是单独的方法函数。如果方法写在prototype上,就只会有一份,更新时候会做到同步更新。
组合继承 将原型链和借用构造函数的技术组合到一块,从而发挥二者之长的一种继承模式。
使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。
function SuperType (name) { this .name = name; this .colors = ['red' , 'blue' , 'green' ]; } SuperType .prototype .sayName = function ( ) { console .log (this .name ); } function SubType (name, age) { SuperType .call (this , name); this .age = age; } SubType .prototype = new SuperType ();SubType .prototype .constructor = SubType ;SubType .prototype .sayAge = function ( ) { console .log (this .age ); }; var instance1 = new SubType ('Tom' , 20 );instance1.colors .push ('black' ); console .log (instance1.colors ); instance1.sayName (); instance1.sayAge (); var instance2 = new SubType ('Peter' , 30 );console .log (instance2.colors ); instance2.sayName (); instance2.sayAge ();
缺点: 调用了两次父类构造函数,一次通过SuperType.call(this)调用,一次通过new SuperType()调用。
原型式继承 不使用严格意义上的构造函数,借助原型可以基于已有的对象创建新的对象,同时还不必因此创建自定义类型。
function object (o) { function F ( ) {} F.prototype = o; return new F (); } var person = { name : 'Tom' , friends : ['Shelby' , 'Court' , 'Van' ] }; var anotherPerson = object (person);anotherPerson.name = 'Greg' ; anotherPerson.friends .push ('Rob' ); var yetAnotherPerson = object (person);yetAnotherPerson.name = 'Linda' ; yetAnotherPerson.friends .push ('Barbie' ); console .log (anotherPerson.friends ); console .log (yetAnotherPerson.friends ); console .log (person.friends );
Object.create()在传入一个参数的情况下与前面写的object()方法的行为相同
var person = { name : 'Tom' , friends : ['Shelby' , 'Court' , 'Van' ] }; var anotherPerson = Object .create (person);anotherPerson.name = 'Greg' ; anotherPerson.friends .push ('Rob' ); var yetAnotherPerson = Object .create (person, { name : { value : 'Linda' , enumerable : true } }); yetAnotherPerson.friends .push ('Barbie' ); console .log (anotherPerson.friends ); console .log (yetAnotherPerson.friends ); console .log (person.friends );
缺点:和原型链继承一样,所有子类实例共享父类的引用类型。
寄生式继承 寄生式继承是与原型式继承紧密相关的一种思路,创建一个仅用于封装继承过程的函数,该函数内部以某种形式来做增强对象,最后返回对象
function object (o) { function F ( ) {} F.prototype = o; return new F (); } function createAnother (o) { var clone = object (o); clone.sayHi = function ( ) { console .log ('Hi' ); } return clone; } var person = { name : 'Tom' , friends : ['Shelby' , 'Court' , 'Van' ] }; var anotherPerson = createAnother (person);anotherPerson.sayHi (); anotherPerson.friends .push ('Rob' ); console .log (anotherPerson.friends ); var yerAnotherPerson = createAnother (person);console .log (yerAnotherPerson.friends );
缺点: 和原型链式继承一样,所有子类实例共享父类引用类型。 和借用构造函数继承一样,每次创建对象都会创建一次方法。
寄生组合式继承 将寄生式继承和组合继承相结合,解决了组合式继承中会调用两次父类构造函数的缺点。
组合继承在第一次调用SuperType构造函数时,SubType.prototype会得到两个属性:name和colors;它们都是 SuperType 的实例属性,只不过现在位于 SubType的原型中。当调用SubType构造函数时,又会调用一次SuperType构造函数,这一次又在新对象上创建了实例属性name和colors。于是,这两个属性就屏蔽了原型中的两个同名属性。
所谓寄生组合式继承,即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。
其背后的基本思路是:不必为了指定子类型的原型而调用父类的构造函数,我们需要的无非就是父类原型的一个副本而已。本质上,就是使用寄生式继承来继承父类的prototype,然后再将结果指定给子类的prototype。
function object (o ) { function F ( ) { } F.prototype = o; return new F (); } function inheritPrototype (SubType, SuperType ) { var prototype = object (SuperType .prototype ); prototype.constructor = SubType ; SubType .prototype = prototype; } function SuperType (name ) { this .name = name; this .colors = ["red" , "blue" , "green" ]; } SuperType .prototype .sayName = function ( ) { console .log (this .name ); }; function SubType (name, age ) { SuperType .call (this , name); this .age = age; } inheritPrototype (SubType , SuperType );SubType .prototype .sayAge = function ( ) { console .log (this .age ); }; var instance1 = new SubType ('Tom' , 20 );instance1.colors .push ('black' ); instance1.sayAge (); instance1.sayName (); console .log (instance1.colors ); var instance2 = new SubType ('Peter' , 30 );instance2.sayAge (); instance2.sayName (); console .log (instance2.colors );
寄生组合式继承的高效率体现在它只调用了一次SuperType构造函数,并且因此避免了再SubType.prototype上面创建不必要的、多余的属性。与此同时,原型链还能保持不变。因此,还能够正常使用instanceof和isPrototypeOf()。
ES6实现继承 class SuperType { constructor (name ) { this .name = name; this .colors = ["red" , "blue" , "green" ]; } sayName ( ) { console .log (this .name ); }; } class SubType extends SuperType { constructor (name, age ) { super (name); this .age = age; } sayAge ( ) { console .log (this .age ); } } var instance1 = new SubType ('Tom' , 20 );instance1.colors .push ('black' ); instance1.sayAge (); instance1.sayName (); console .log (instance1.colors ); var instance2 = new SubType ('Peter' , 30 );instance2.sayAge (); instance2.sayName (); console .log (instance2.colors );