在面向对象中,继承分为接口继承实现继承。而JavaScript中,ECMAScript中无法实现接口继承,只支持实现继承,而实现继承主要依靠的是原型链来实现的。

在JS中实现继承的方式有如下6种:

1. 原型链

2. 借用构造函数

3. 组合继承

4. 原型式继承

5. 寄生式继承

6. 寄生组合式继承

原型链

ECMAScript中描述了原型链的概念,并将原型链作为实现继承的主要方法。其基本思想是利用原型链 上让一个引用类型继承另一个引用类型的属性和方法。每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个纸箱原型对象的内部指针。

  1. // 《JavaScript高级程序设计》中给出了如下实现方式
  2. function SuperType(){
  3. this.property = true;
  4. }
  5. SuperType.prototype.getSuperValue = function(){
  6. return this.property;
  7. }
  8. function SubType(){
  9. this.subProperty = false;
  10. }
  11. //继承 SuperType
  12. SubType.prototype = new SuperType();//SubType.prototype被重写,SubType.prototype.constructor也一同被重写
  13. SubType.prototype.getSubVaule = function(){
  14. return this.subProperty;
  15. }
  16. // 比较严谨的写法要添加下边这段
  17. SuperType.prototype.getSuperValue = function(){
  18. return false;
  19. }
  20. var instance = new SubType();
  21. alert(instance.getSuperValue());//true

但是这种写法存在一个很严重的问题:

  1. 当原型链中包含引用类型值的原型时,该引用类型值会被所有实例共享;

  2. 在创建子类型(例如创建SubType实例)时,不能向超类型(SuperType)的构造函数中传递参数。

构造函数

为了解决上面的两个问题,所以要借用构造函数的技术(也叫经典继承)。主要原理就是在子类型构造函数的内部调用超类型构造函数。

  1. function SuperType(){
  2. this.colors = ["red","blue","green"];
  3. }
  4. function SubType(){
  5. //继承了SuperType,且向父类型传递参数
  6. SuperType.call(this);
  7. }
  8. var instance1 = new SubType();
  9. instance1.colors.push("black")
  10. console.log(instance1.colors);//"red,blue,green,black"
  11. var instance2 = new SubType(); console.log(instance2.colors);//"red,blue,green" 可见引用类型值是独立的

借用构造函数的方式解决了上面原型链方法带来的问题:

  1. 保证了原型链中引用类型值的独立,不再被所有实例共享;

  2. 子类型在创建时也能向父类型传递参数。

但是如果仅仅借用构造函数,那么将无法避免构造函数模式存在的问题—方法都在构造函数中定义,因此函数复用就无从谈起了。而且,在超类型的原型中定义的方法,对子类型而言也是不可见的。结果所有类型都只能使用构造函数模式。考虑到这些,借用构造函数的技术也是很少单独使用的。

组合继承

组合继承,也称作伪经典继承,指的是将原型链和借用构造函数的技术组合到一块。其背后的思路是使用原型属性和方法的继承,而通过借用构造函数来实现对实力属性的继承。这样,即通过原型上定义方法实现了函数复用,又能保证每个势力都有自己的属性。

  1. function SuperType(name){
  2. this.name = name;
  3. this.colors = ["red","blue","green"];
  4. }
  5. SuperType.prototype.sayName = function(){
  6. alert(this.name);
  7. };
  8. function SubType(name,age){
  9. SuperType.call(this,name);//继承实例属性,第一次调用SuperType() t
  10. this.age = age;
  11. }
  12. SubType.prototype = new SuperType();//继承父类方法,第二次调用SuperType()
  13. SubType.prototype.constructor = SubType;
  14. SubType.prototype.sayAge = function(){
  15. alert(this.age);
  16. }
  17. var instance1 = new SubType("louis",5);
  18. instance1.colors.push("black"); console.log(instance1.colors);//"red,blue,green,black" =
  19. instance1.sayName();//louis
  20. instance1.sayAge();//5
  21. var instance1 = new Son("zhai",10); console.log(instance1.colors);//"red,blue,green"
  22. instance1.sayName();//zhai
  23. instance1.sayAge();//10

组合继承避免了原型链和借用构造函数的缺陷,融合了他们的优点,成为JavaScript中最常见的继承模式,而且,instanceof和isPrototypeOf()也能用于识别基本组合继承的对象。但是调用了两次父类构造函数,造成了不必要的消耗。

原型继承

基本思路是借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型。在object()函数内部,先创建一个历史性的构造函数,然后将传入对象作为这个构造函数的原型,最后返回了这个临时类型的一个新实例。从本质上讲,object()对传入其中的对象执行了一次浅拷贝。

在ES5中,新增了一个 Object.create()方法规范了原型式继承。这个方法接受两个参数:一个是用做新对象原型的对象和一个为新对象定义额外属性的对象。在传入一个参数的情况下,和Object()方法行为相同。

  1. var person = {
  2. name: 'Jokul',
  3. friends:['JS','CSS','HTML']
  4. }
  5. var anotherPerson = Object.create(person, { name: { value: 'HEIHEI' } })
  6. alert(anotherPerson.name) //"HEIHEI"

注意:在原型继承中,包含引用类型值的属性,始终都会共享相应的值,就像使用原型模式一样。

寄生式继承

寄生式继承是与原型式继承紧密相关的一种思路。它的思路与寄生构造函数和工厂模式类似,即创建一个仅用于封装继承过程的函数,该函数在内部已某种方式来增强对象,最后再像真地是它做了所有工作一样返回对象。

  1. function createAnother(original){
  2. var clone = object(original);//通过调用object函数创建一个新对象
  3. clone.sayHi = function(){//以某种方式来增强这个对象
  4. alert("hi");
  5. };
  6. return clone;//返回这个对象
  7. }

注意:使用寄生式继承来为对象添加函数,会由于不能做到函数复用而降低效率。

寄生组合式继承

组合继承是JavaScript最常见的继承模式,不过它最大的缺点就是无论在什么时候,都会调用两次超类型构造函数:一次是在创建子类型原型的时候;一次是在子类型构造函数内部。寄生组合式继承就是为了降低调用父类构造函数的开销而出现的。

  1. function inheritPrototype(subClass,superClass){
  2. var prototype = object(superClass.prototype);//创建对象
  3. prototype.constructor = subClass;//增强对象
  4. subClass.prototype = prototype;//指定对象
  5. }

终极版继承实现方式

  1. function Rectangle(length,width){
  2. this.l = length
  3. this.w = width
  4. }
  5. Rectangle.prototype.getArea = function(){
  6. return this.l*this.w
  7. }
  8. function Square(length){
  9. // 继承Rectangle的变量及私有变量
  10. Rectangle.call(this,length,length)
  11. }
  12. // Square对Rectangle的原型的继承 将Square写入Rectangle的constructor
  13. Square.prototype = Object.create(Rectangle.prototype,{
  14. constructor:{
  15. value:Square
  16. }
  17. })
  18. // 上面这段等于下边的,
  19. // Rectangle.prototype.constructor = Square
  20. var square = new Square(3)
  21. console.log(square.getArea())
  22. console.log(square instanceof Square)
  23. console.log(square instanceof Rectangle)

New操作符

new操作符具体进行下面这三步操作:

  1. 创建一个空对象;

  2. 将这个对象的proto成员指向父函数对象的prototype成员对象;

  3. 将父函数对象的this指针替换成子对象,然后在调用父函数

https://juejin.im/post/58f94c9bb123db411953691b#heading-3

https://juejin.im/post/5b654e88f265da0f4a4e914c#heading-0

https://juejin.im/post/58f94c9bb123db411953691b