JavaScript实现继承的几种重要范式

一 原型链

1. 代码示例

function SuperType() {
  this.superProperty = true;
}

SuperType.prototype.getSuperValue = function() {
  return this.superProperty;
}

function SubType() {
  this.subProperty = false;
}

SubType.prototype = new SuperType(); //将 SuperType类型的实例 做为 SubType类型的 原型对象, 这样就重写了SubType的原型对象(没有使用SubType默认的原型对象), 实现了继承。

SubType.prototype.getSubValue = function() {
  return this.subProperty;
}

const subTypeInstance = new SubType();
console.log(subTypeInstance.getSuperValue());//true

详见个人另外一篇博客《原型与原型链》 的"2、实现继承的主要范式:原型链
"。html

2、 借用构造函数(经典继承)

1.代码示例

function SuperType() {
  this.colors = ['red', 'blue', 'green'];
}

function SubType() {
  SuperType.call(this);//在子类型构造函数内部调用超类型构造函数,继承了SuperType
}

var subTypeInstance1 = new SubType();
subTypeInstance1.colors.push('yellow');
console.log(subTypeInstance1);//['red', 'blue', 'green','yellow']

var subTypeInstance2 = new SubType();
console.log(subTypeInstance2.colors);//['red', 'blue', 'green']

基本思想就是在子类型构造函数内部调用超类型构造函数。es6

2. 优势

(1)子类型每一个实例都会继承一份独立的超类型属性副本

经过使用call方法(或apply),其实是在将来新建立子类型实例时当场调用了超类型的构造函数,也就是在初始化子类型实例时才把超类型的属性添加到子类型实例上。那么子类型的每一个实例都会拥有一份独立的超类型属性副本。 这样不一样的子类型实例对同一个继承来的属性进行修改(例如对数组属性进行push),也不会互相影响。数组

(2)能够在子类型构造函数中向超类型构造函数传递参数
function SuperType(name) {
  this.name = name;
}

function SubType(name) {
  SuperType.call(this,name);
}

var subTypeInstance1 = new SubType('Bonnie');
console.log(subTypeInstance1.name);//"Bonnie"

var subTypeInstance2 = new SubType('Summer');
console.log(subTypeInstance2.name);//"Summer"

3. 缺点

(1)不能作到函数复用:没法避免构造函数模式的问题——使用构造函数模式建立的每一个实例都包含着各自独有的同名函数,故函数复用无从谈起。app

(2)子类型建立方式限制:而在超类型的原型中定义的方法,对子类而言是不可见的,因此全部子类型都只能经过构造函数模式建立。函数

3、 组合继承(伪经典继承)

1. 代码示例

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); //第二次调用超类型构造函数SuperType
  //本身的属性
  this.age = age;
}

//继承方法:SubType.prototype也会获得继承的属性,不过会被上述构造函数中call方法继承的属性做为实例属性覆盖掉。
SubType.prototype = new SuperType(); //第一次调用超类型构造函数SuperType
SubType.prototype.constructor = SubType;//重写prototype会割裂子类型原型与子类型构造函数的关系,故要加上这么一句
//本身的方法
SubType.sayAge = function() {
  console.log(this.age);
}

将原型链和借用构造函数组合起来:使用原型链实现对超类型原型上的方法和属性的继承,使用借用构造函数实现对超类型实例属性和方法的继承。通常超类型原型上就只有方法,超类型实例上只有属性(即 组合使用构造函数模式和原型模式,参见3.4)。这样一来,该方式就是:用原型链实现对超类型原型上方法的继承,用借用构造函数实现对超类型实例上属性的继承。ui

2. 优势

组合继承用 原型链实现对 超类型原型上方法的继承,用 借用构造函数实现对 超类型实例上属性的继承。这样可让子类型的不一样实例既分别拥有独立的属性(尤为是引用类型属性,如colors数组),又能够共享相同的方法。this

组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优势,是 JavaScript中最经常使用的继承模式prototype

3. 缺点

不管在什么状况下,都会调用两次超类型构造函数:一次是在建立子类型原型时,一次是在子类型构造函数内部。这样的话,虽然子类型会包含超类型的所有属性,可是对于超类型的实例属性而言,调用子类型的构造函数时会重写一遍这些实例属性。设计

4、 原型式继承

1. 代码示例

function object(o) {
  function F(){};//先构造一个临时性的构造函数
  F.prototype = o;//将传入的对象做为该构造函数的原型
  return new F();//返回临时类型的新实例
}

//使用
var person = {
  name:'Bonnie',
  friends: ['Summer', 'Spring']
}

var person1 = object(person);
person1.name = 'Huiyun';
person1.friends.push('Tony');

var person2 = object(person);
person2.name = 'Huiyun';
person2.friends.push('Joy');

console.log(person1.friends);//["Summer", "Spring", "Tony", "Joy"]
console.log(person2.friends);//["Summer", "Spring", "Tony", "Joy"]
console.log(person.friends);//["Summer", "Spring", "Tony", "Joy"]

思想: 借助原型能够基于已有的对象(而非类型)建立新对象(而非建立自定义类型)。其实,object对传入其中的对象执行了一次 浅复制code

2. 优势

能够基于已有的对象建立新对象,并且还没必要建立新类型。适于基于已有对象加以修改获得另外一个对象。

在只想让一个对象与另外一个对象保持相似,又不想兴师动众建立构造函数的状况下,原型式继承彻底能够胜任。

延伸:ES6的Object.create()

该方法规范化了原型式继承,在只传入一个参数的状况下,和上述object达到的效果相同。该方法的第二个参数为新对象额外属性组成的对象,也就是简化了上述object的后续用法。

语法:
Object.create(protoObj, [newPropertiesObj])

参数:

  • protoObj: 新建立对象的原型对象
  • newPropertiesObj:可选。 要添加(或重写)到新对象上的可枚举的实例属性的属性描述符及其名称组成的对象(与Object.defineProperties()的第二个参数相同)。

newPropertyiesObj语法:

{
  prop1Name:{
    value: valueConent,
    writable: false(default)/true
    enumerable: false(default)/true
    configuragle: false(default)/true
  },
  prop2Name: {
    ...
  },
  ...
}

详见https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperties

返回值: 一个带有指定的原型对象属性和本身新添加的实例属性的对象

Eg:
var person = {
  name: 'Bonnie',
  friends: ['Summer','Spring']
}

var person1 = Object.create(person, {
  name:{
   value:'Huiyun'
  }
});

console.log(person1);//{name: "Huiyun"}
console.log(person1.friends);// ["Summer", "Spring"]

3.缺点

引用类型属性共享: 该方法基于已有对象建立新对象,可是对新对象修改引用类型属性,已有的基础对象也会受到修改,基于基础对象的其余对象也会受到修改。

5、 寄生式继承

1. 代码示例

function createAnother(original) {
  var clone = object(original);//此处运用了4.4中的objec函数,也可使用Object.create(original)
  clone.sayHi = function() {
    console.log('Hi');
  }
  return clone;
}

思想:与建立对象的寄生构造函数模式和工厂模式相似,即建立了一个仅用于封装继承过程的函数,并在该函数内部以某种方式来加强对象,最后再像真地是它本身作了全部工做同样返回对象。

2. 优势

在主要考虑基于某个对象而非考虑自定义类型和构造函数的状况下,寄生式继承也是一种有用的模式。该模式能够基于已有对象建立添加了函数的新对象。

3. 缺点

(1)不能作到函数复用:不能作到对新添加函数进行函数复用,这样会下降效率。该缺点与 借用构造函数继承相似。

(2)引用类型属性共享:该方法也是基于已有对象建立新对象,可是对新对象修改引用类型属性,已有的基础对象也会受到修改,基于基础对象的其余对象也会受到修改。该缺点 与原型式继承 同样。

6、 寄生组合式继承

1. 代码示例

//实际上是寄生式继承的一种应用:以SuperType.prototype为基础对象,建立SubType.prototype对象。这个SubType.prototype对象是SuperType.prototype的浅复制,同时SubType.prototype对象上又增添了额外的属性constructor指向SubType。
function inheritPrototype(SubType, SuperType) {
  var prototype = Object.create(SuperType.prototype);
  prototype.constructor = SubType;
  Subtype.prototype = prototype;
}

//应用
function SuperType(name) {
  this.name = name;
  this.colors = ['red', 'blue'];
}
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);
}

思想: 子类型的实例经过借用构造函数来获取超类型实例上的属性,子类型的原型经过寄生式继承来获取超类型原型上的方法(此处寄生式继承是指子类型原型对超类型原型进行浅复制)。也就是说子类型经过借用构造函数继承属性,经过寄生式继承来继承方法。 和组合式继承相比,该寄生组合式继承没必要为了指定子类型的原型而调用超类型的构造函数,只是使用了超类型原型的一个副本;而只有在指定子类型的实例属性时调用了超类型的构造函数(借用构造函数继承);这样该方法就只调用了一次超类型的构造函数。

2. 优势

(1) 属性独立、方法共享:拥有组合式继承的全部优势:分别拥有独立的属性(尤为是引用类型属性,如colors数组),又能够共享相同的方法。

(2) 高效率:避免了组合式继承调用两次超类型构造函数的缺点,只调用一次超类型构造函数,具备高效率。

(3) 原型链不变:可以正常使用instanceof和isPrototypeOf()

该寄生组合式继承是最理想的继承范式。

7、Es6的Class对继承的实现*

参考资料

《JavaScirpt高级程序设计》6.3
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperties

相关文章
相关标签/搜索