JavaScript继承的五种方式

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"; // 覆盖了原型对象person中的name值
firstPerson.friends.push("d"); // 增加了原型对象person引用类型friends中的元素

let secondPerson = Object.create(person);
secondPerson.name = "mm";
secondPerson.friends.push("e");

console.log(person.name); // 原型对象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); // 第二次调用SuperType()
this.age = age;
}

SubType.prototype = new SuperType(); // 第一次调用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); // xyg
console.log(instance1.colors); // ['red', 'blue', 'green']
instance1.sayAge(); // 24
instance1.sayName(); // 'xyg'
  • 相比组合式继承,寄生组合式继承的改变是:将父类构造函数的原型副本赋值给子类构造函数的原型,这样子类就可以继承父类的方法。
  • new 子类构造函数生成实例时就只调用一次父类构造函数,原型上的name和colors没有屏蔽一说。