1. 借用构造函数
解决原型中包含引用类型值多带来的问题。即在子类型构造函数内部调用父类型构造函数,可以在子类构造函数内部通过apply()和call()方法来执行父类构造函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| function SuperType() { this.colors = ["red", "blue", "green"]; }
function SubType() { SuperType.call(this); }
let instance1 = new SubType(); instance1.colors.push("black"); console.log(instance1.colors);
let instance2 = new SubType(); console.log(instance2.colors);
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| function SuperType(name) { this.name = name; }
function SubType() { SuperType.call(this, "xyg"); this.age = 29; }
let instance = new SubType(); console.log(instance.name); console.log(instance.age);
|
- 优势: 可以在子类构造函数中向父类构造函数传递参数
- 缺点: 构造函数模式普遍存在的问题–方法都在构造函数内部定义,函数复用无从说起;而且在父类中定义的方法对子类也是不可见的。
2. 原型式继承
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| let person = { name: "xyg", friends: ["a", "b", "c"] };
let firstPerson = Object.create(person); firstPerson.name = "gg"; firstPerson.friends.push("d");
let secondPerson = Object.create(person); secondPerson.name = "mm"; secondPerson.friends.push("e");
console.log(person.name); console.log(person.friends);
|
- Object.create()方法接收两个参数:一个用作新对象原型的对象和一个为新对象定义额外属性的对象
- 包含引用类型值的属性始终都会共享相应的值
3. 组合继承
将原型链和借用构造函数相结合,使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。即通过在原型上定义方法实现了函数复用,又能够保证每个实例都有它自己的属性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| 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.sayAge = function() { console.log(this.age); }
let instance1 = new SubType("xyg", 24); instance1.colors.push("black"); console.log(instance1.colors); instance1.sayName(); instance1.sayAge();
let instance2 = new SubType("bwm", 25); console.log(instance2.colors); instance2.sayName(); instance2.sayAge();
|
- 在这个例子中,SuperType构造函数定义了两个属性: name和colors。SuperType的原型定义了一个方法sayName。SubType构造函数在调用SuperType传入参数name同时定义了自己的属性age;通过原型继承了SuperType的方法sayName,而后在SubType自己的原型上多定义了自己的一个方法sayAge。这样就可以让两个不同的实例分别拥有自己的属性,又可以使用相同的方法了。
- instanceof和isPrototypeOf()能够用于识别基于组合继承创建的对象。
4. 寄生式继承
寄生式继承是与原型式继承思路相关的一种继承方式。创建一个用于封装继承过程的函数,该函数在内部以某种方式新建对象并增强对象的属性方法,最后再返回该新建的对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| function createAnother(obj) { var clone = Object(obj); clone.sayHi = function() { console.log('hi'); } return clone; }
let person = { name: 'xyg', friends: ['a', 'b', 'c'] }
let anotherPerson = createAnother(person); anotherPerson.sayHi();
|
5. 寄生组合式继承
上文提到的组合继承有不足之处,就是无论什么情况下都会调用两次父类型构造函数:一次是在创建子类型原型的时候,另一次是在子类构造函数内部。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 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); }
|
- 第一次调用SuperType构造函数时,SubType.prototype会得到两个属性: name和colors
- 第二次调用SubType时,SubType内部会再次调用SuperType构造函数,又会在新对象的原型上创建了实例属性name和colors,这两个实例属性就会屏蔽原型中的两个同名属性
寄生组合式继承
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| function inheritPrototype(subType, superType) { let 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); }
let instance1 = new SubType('xyg', 24); console.log(instance1.name); console.log(instance1.colors); instance1.sayAge(); instance1.sayName();
|
- 相比组合式继承,寄生组合式继承的改变是:将父类构造函数的原型副本赋值给子类构造函数的原型,这样子类就可以继承父类的方法。
- new 子类构造函数生成实例时就只调用一次父类构造函数,原型上的name和colors没有屏蔽一说。