js对象继承
如果一个对象想要用到另一个对象的方法属性时,用继承来实现无疑是最好的方法,这就像慕容家族的以彼之道还施彼身一样,我可以通过继承来拿到你所有的对象和方法。一般的OO语言有接口继承和实现继承两种继承方式。js只支持实现继承,而且实现主要通过原型链来实现的。
具体实现继承一般有六种方法:
1.原型链
基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。每个构造函数都有一个原型对象(prototype),原型对象都包含一个指向构造函数的指针(constructor),每一个实例都包含一个纸箱原型对象的内部指针([[prorotype]])。如果让一个原型对象邓毅另一个类型的实例,此时的原型对象将包含一个指向另一个原型的指针,假如另一个原型又是另一个类型的实例。n那么上述关系依然成立,如此层层递进,就构成了实例与原型的链条,这就是原型链。
示例如下:
function Super(name){ this.prototype= true; } Super.prototype.getSupervalue= function(){ return this.prototype; } function Sub(){ this.subprototype = false; } //Sub的原型等于Super的实例,继承Super及其原型的属性和方法,继承的方法属性在原型上 Sub.prototype = new Super(); //添加新方法 Sub.prototype.getSubvalue = function(){ return this.subprototype; } //重写超类型中的方法 Sub.prototype.getSupervalue = function(){ return false; } var instance = new Sub(); alert(instance.getSupervalue());//false
这就是原型链的方法,缺点是如果超类型中的属性是引用类型,则由于所有实例共享超类型的属性,其实都是引用的一个引用类型的地址指针,一个实例更改超类型引用类型属性的值,所有实例都会反映。另一个缺点是无法向超类型传值。
原型链继承的属性方法都在新实例的原型上。
2.借用构造函数
借用构造函数的思想很简单,在子类型构造函数的内部调用超类型构造函数。函数只不过是在特定环境中执行代码的对象。因此通过apply()和call()方法也可以在将要新创建的对象上执行构造函数,示例如下:
function Super(name){ this.colors = ['red','blue','green']; this.name = name; } function Sub(){ //通过运行Super的方法实现继承,创建实例时继承继承的方法属性都在实例上 Super.call(this,'jone'); } var instance1 = new Sub(); instance1.colors.push('black'); alert(instance1.colors); // 'red,blue,green,black' alert(instance1.name); //'jone' var instance2 = new Sub(); alert(instance1.colors); // 'red,blue,green'
借用构造函数继承的本质是在构造函数中通过绑定this运行超类型的方法从而实现继承,这种方法解决了原型链无法传值和引用类型属性共享的问题,但是也带来了新的问题,由于方法都写在构造函数中因此无法复用。而且超类型原型中定义的方法属性无法得到继承。
借用构造函数继承,在创建实例时运行超类型构造函数,继承的属性方法在实例上,无法继承超类型原型上的属性方法。
3.组合继承
组合继承指的是将原型链和构造函数的技术组合到一块,综合二者之长的一种继承模式。主要思路是使用原型链实现对原型属性和方法的继承,而通过构造函数来实现对实例属性的继承。
function Super(name){ this.colors = ['red','blue','green']; this.name = name; } Super.prototype.sayName = function(){ console.log(this.name); } function Sub(name,age){//借用构造函数继承,主要是创建实例时继承,继承在实例上 Super.call(this,name); this.age = age; } Sub.prototype = new Super();//原型链继承,主要继承原型的属性方法 Sub.prototype.sayAge = function(){ alert(this.age); } var instance1 = new Sub('jone',29); instance1.colors.push('black'); alert(instance1.colors); // 'red,blue,green,black' instance1.sayName(); //'jone' instance1.sayAge(); //29 var instance2 = new Sub('greg',32); alert(instance2.colors); // 'red,blue,green' instance2.sayName(); //'greg' instance2.sayAge(); //32
组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优点,是最常用的继承模式,而且instanceof 和isPrototypeOf()也能够用于识别基于组合继承创建的对象。
组合继承,原型链实现对原型属性和方法的继承,而通过构造函数来实现对实例属性的继承。
4.原型式继承
这种方法灭有严格意义上的构造函数,主要是借助原型可以基于已有的对象创建新对象,同事还不必为此创建自定义类型,主要运用了以下函数:
function object(o){ function F(){}//构造函数 F.prototype = o;//原型链继承 return new F();//返回实例 }
前面几种方法都需要知道构造函数,原型式继承不需要知道构造函数,只需要一个对象实例,通过opject函数对传入的对象执行了一次浅复制。es5通过Object。create()方法规范了原型式继承,接受两个参数:一个用作新对象原型的对象和(可选的)一个为新对象定义额外属性的对象。传入一个对象时与object()方法行为相同,第二个参数与Object.definProperties()方法的第二个参数格式相同。
在没有必要兴师动众创建构造函数只想让一个对象与另一个对象保持类似的情况下,原型式继承完全可以胜任。但是原型式继承的缺点是包含引用类型的属性始终都会被实例所共享,这点与原型链式继承一样的。
封装一个函数可以借助一个已知对象实例可以创建一个实现继承的新实例并返回,方便快捷。
5.寄生式继承
寄生式继承和原型式继承差不多,只不过在寄生式继承的基础上对新对象实例进行了加强。寄生式继承的思路与寄生构造函数和工厂模式类似。创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后返回对象。示例如下:
function createAnother(original){ var clone = object(original);//通过调用函数创建一个新对象 clone.sayName = function(){//以某种方式加强这个对象 alert('hello'); }; return clone;//返回对象实例 }
在主要考虑对象而不是自定义类型和构造函数的情况下,寄生式也是一种有用的模式。这种方式也是借助实例来创建实现继承的新实例。缺点是为对象添加函数时函数无法复用。
封装一个函数借助一个已知对象实例可以创建一个实现继承的新实例并用某种方法增强对象最后返回对象。
6.组合寄生模式
组合寄生模式就是对组合继承方式的完善,组合继承是js最常用的继承模式,它最大的不足是无论什么情况下都会两次调用超类型构造函数,而用寄生组合模式可以避免这个问题。
function Super(name){ this.colors = ['red','blue','green']; this.name = name; } Super.prototype.sayName = function(){ console.log(this.name); } function Sub(name,age){ Super.call(this,name);//第二次调用Super() this.age = age; } Sub.prototype = new Super();//第一次调用Super() Sub.prototype.sayAge = function(){ alert(this.age); } var instance1 = new Sub('jone',29);
所谓寄生组合继承,即通过借用构造函数继承属性,通过原型链的混成形式来继承方法。基本思路是:不必为了制定子类型的原型而调用超类型的构造函数,我们需要的无非就是超类型原型的一个副本而已。本质上就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。基本模式如下:
function inheritPrototype(Sub,Super){//针对构造函数 var prototype = object(Super.prototype);//创建对象 prototype.constructor = Sub;//增强对象 Sub.prototype = prototype; //指定原型 }
用寄生继承模式来实现原型的继承,这个函数接受两个参数,子类型的构造函数和超类型的构造函数。创建超类型的副本,重写constructor属性,把新创建的对象(即副本)赋值给子类型的原型。完整示例如下:
function Super(name){ this.name = name; } Super.prototype.say = function(){ console.log(this.name); } function Sub(name,age){//借用构造函数实现示例继承 Super.call(this,name); this.age = age; } inheritPrototype(Sub,Super);//寄生继承实现原型继承 var instance = new Sub('jone',28); instance.say();//jone
寄生组合模式避免了超类型构造函数的二次调用,借用寄生式继承来使子类型原型继承超类型原型的属性方法。只调用了一次超类型对象的构造函数,避免了在Sub.prototype上创建多余的、不必要的属性。同时原型链不变,还能正常使用instanceof和isPrototypeOf()。普遍认为寄生组合模式是引用类型最理想的继承模式。
寄生组合式继承和组合继承使用场景一样,本质上是对组合继承的完善。
总结一下:
原型链继承是基础,一般由于存在引用类型属性共用等问题,一般不单独使用(是基础,相当于内功心法,不能没有,但是只有这一个也不行)
借用构造函数,可以完整继承构造函数上定义的属性,但是原型属性无法继承 (比较轻快,相当于擒拿手,无内力,没办法继承原型)
组合继承,综合原型链继承和借用构造函数,比较常用(内功心法加擒拿比较重,相当于降龙十八掌,有副作用,两次调用超类型构造函数)
原型式继承,借用实例来创建继承后的新实例(比较灵活,无需借助构造函数,相当于葵花点穴手,出招快捷方便,但是与原型链继承一样,会有引用类型属性共用问题)
寄生式继承,本质和原始式继承一样,只是用某种方法对新实例进行了增强(加强版的点穴功一阳指)
寄生组合式继承,组合式继承的完善版,综合运用借用构造函数和寄生组合式(内功心法加一阳指,武林神功六脉神剑)