在面向对象中,继承分为接口继承和实现继承。而JavaScript中,ECMAScript中无法实现接口继承,只支持实现继承,而实现继承主要依靠的是原型链来实现的。
在JS中实现继承的方式有如下6种:
1. 原型链
2. 借用构造函数
3. 组合继承
4. 原型式继承
5. 寄生式继承
6. 寄生组合式继承
原型链
ECMAScript中描述了原型链的概念,并将原型链作为实现继承的主要方法。其基本思想是利用原型链 上让一个引用类型继承另一个引用类型的属性和方法。每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个纸箱原型对象的内部指针。
// 《JavaScript高级程序设计》中给出了如下实现方式function SuperType(){this.property = true;}SuperType.prototype.getSuperValue = function(){return this.property;}function SubType(){this.subProperty = false;}//继承 SuperTypeSubType.prototype = new SuperType();//SubType.prototype被重写,SubType.prototype.constructor也一同被重写SubType.prototype.getSubVaule = function(){return this.subProperty;}// 比较严谨的写法要添加下边这段SuperType.prototype.getSuperValue = function(){return false;}var instance = new SubType();alert(instance.getSuperValue());//true
但是这种写法存在一个很严重的问题:
当原型链中包含引用类型值的原型时,该引用类型值会被所有实例共享;
在创建子类型(例如创建SubType实例)时,不能向超类型(SuperType)的构造函数中传递参数。
构造函数
为了解决上面的两个问题,所以要借用构造函数的技术(也叫经典继承)。主要原理就是在子类型构造函数的内部调用超类型构造函数。
function SuperType(){this.colors = ["red","blue","green"];}function SubType(){//继承了SuperType,且向父类型传递参数SuperType.call(this);}var instance1 = new SubType();instance1.colors.push("black")console.log(instance1.colors);//"red,blue,green,black"var instance2 = new SubType(); console.log(instance2.colors);//"red,blue,green" 可见引用类型值是独立的
借用构造函数的方式解决了上面原型链方法带来的问题:
保证了原型链中引用类型值的独立,不再被所有实例共享;
子类型在创建时也能向父类型传递参数。
但是如果仅仅借用构造函数,那么将无法避免构造函数模式存在的问题—方法都在构造函数中定义,因此函数复用就无从谈起了。而且,在超类型的原型中定义的方法,对子类型而言也是不可见的。结果所有类型都只能使用构造函数模式。考虑到这些,借用构造函数的技术也是很少单独使用的。
组合继承
组合继承,也称作伪经典继承,指的是将原型链和借用构造函数的技术组合到一块。其背后的思路是使用原型属性和方法的继承,而通过借用构造函数来实现对实力属性的继承。这样,即通过原型上定义方法实现了函数复用,又能保证每个势力都有自己的属性。
function SuperType(name){this.name = name;this.colors = ["red","blue","green"];}SuperType.prototype.sayName = function(){alert(this.name);};function SubType(name,age){SuperType.call(this,name);//继承实例属性,第一次调用SuperType() tthis.age = age;}SubType.prototype = new SuperType();//继承父类方法,第二次调用SuperType()SubType.prototype.constructor = SubType;SubType.prototype.sayAge = function(){alert(this.age);}var instance1 = new SubType("louis",5);instance1.colors.push("black"); console.log(instance1.colors);//"red,blue,green,black" =instance1.sayName();//louisinstance1.sayAge();//5var instance1 = new Son("zhai",10); console.log(instance1.colors);//"red,blue,green"instance1.sayName();//zhaiinstance1.sayAge();//10
组合继承避免了原型链和借用构造函数的缺陷,融合了他们的优点,成为JavaScript中最常见的继承模式,而且,instanceof和isPrototypeOf()也能用于识别基本组合继承的对象。但是调用了两次父类构造函数,造成了不必要的消耗。
原型继承
基本思路是借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型。在object()函数内部,先创建一个历史性的构造函数,然后将传入对象作为这个构造函数的原型,最后返回了这个临时类型的一个新实例。从本质上讲,object()对传入其中的对象执行了一次浅拷贝。
在ES5中,新增了一个 Object.create()方法规范了原型式继承。这个方法接受两个参数:一个是用做新对象原型的对象和一个为新对象定义额外属性的对象。在传入一个参数的情况下,和Object()方法行为相同。
var person = {name: 'Jokul',friends:['JS','CSS','HTML']}var anotherPerson = Object.create(person, { name: { value: 'HEIHEI' } })alert(anotherPerson.name) //"HEIHEI"
注意:在原型继承中,包含引用类型值的属性,始终都会共享相应的值,就像使用原型模式一样。
寄生式继承
寄生式继承是与原型式继承紧密相关的一种思路。它的思路与寄生构造函数和工厂模式类似,即创建一个仅用于封装继承过程的函数,该函数在内部已某种方式来增强对象,最后再像真地是它做了所有工作一样返回对象。
function createAnother(original){var clone = object(original);//通过调用object函数创建一个新对象clone.sayHi = function(){//以某种方式来增强这个对象alert("hi");};return clone;//返回这个对象}
注意:使用寄生式继承来为对象添加函数,会由于不能做到函数复用而降低效率。
寄生组合式继承
组合继承是JavaScript最常见的继承模式,不过它最大的缺点就是无论在什么时候,都会调用两次超类型构造函数:一次是在创建子类型原型的时候;一次是在子类型构造函数内部。寄生组合式继承就是为了降低调用父类构造函数的开销而出现的。
function inheritPrototype(subClass,superClass){var prototype = object(superClass.prototype);//创建对象prototype.constructor = subClass;//增强对象subClass.prototype = prototype;//指定对象}
终极版继承实现方式
function Rectangle(length,width){this.l = lengththis.w = width}Rectangle.prototype.getArea = function(){return this.l*this.w}function Square(length){// 继承Rectangle的变量及私有变量Rectangle.call(this,length,length)}// Square对Rectangle的原型的继承 将Square写入Rectangle的constructorSquare.prototype = Object.create(Rectangle.prototype,{constructor:{value:Square}})// 上面这段等于下边的,// Rectangle.prototype.constructor = Squarevar square = new Square(3)console.log(square.getArea())console.log(square instanceof Square)console.log(square instanceof Rectangle)
New操作符
new操作符具体进行下面这三步操作:
创建一个空对象;
将这个对象的proto成员指向父函数对象的prototype成员对象;
将父函数对象的this指针替换成子对象,然后在调用父函数
https://juejin.im/post/58f94c9bb123db411953691b#heading-3
