1. 原型链继承
我们从上一章知道,在 new 运算符操作的时候,会将实例原型(Child.prototype)指向构造函数的原型(Parent.prototype),这样 child1 就可以访问到构造函数原型中的属性,实现了继承。
缺点:
- 多个实例对引用类型的操作会被篡改。这个理解一下原型链就明白了,就不费篇幅写例子了。
- 在创建 Child 的实例时,不能向Parent传参。 ```javascript function Parent () { this.name = ‘name’; }
Parent.prototype.getName = function () { console.log(this.name); }
function Child () {}
Child.prototype = new Parent();
const child1 = new Child();
console.log(child1.getName()) // name
我们可以打印出来验证一下:<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/1105483/1626369740772-4145d492-c787-464e-9274-e65580370df0.png#clientId=uf1a2c660-eaaf-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=505&id=u16ed148c&margin=%5Bobject%20Object%5D&name=image.png&originHeight=505&originWidth=493&originalType=binary&ratio=1&rotation=0&showTitle=false&size=33689&status=done&style=none&taskId=u89d2fdb6-b945-482a-8cc2-2f7f3c0c516&title=&width=493)
<a name="IvuEn"></a>
# 2. 借用构造函数
使用父类的构造函数来实现继承,等同于复制父类的实例给子类(不使用原型)。但在创建子类实例时,会调用 Perent 构造函数,所以 Child 的每个实例都会将 Parent 中的属性复制一份
优点:
1. 避免了引用类型的属性被所有实例共享
2. 可以在 Child 中向 Parent 传参
缺点:
1. 方法都在构造函数中定义,每次创建实例都会创建一遍方法,影响性能
2. 只能继承父类的实例属性和方法,不能继承原型属性/方法
```javascript
function Parent (name) {
this.name = name;
}
function Child (name) {
// 继承于 Parent
Parent.call(this, name);
}
var child1 = new Child("kevin");
console.log(child1.name); // kevin
var child2 = new Child("yayu");
console.log(child2.name); // yayu
3. 组合继承
原型链继承和经典继承双剑合璧,用原型链实现对原型属性和方法的继承,用借用构造函数的方法来实现实例属性的继承。
优点:
- 融合原型链继承和构造函数的优点。
缺点:
- 在使用子类创建实例对象时,其原型中会存在两份相同的属性/方法。 ```javascript function Parent (name) { this.name = name; this.colors = [‘red’, ‘blue’, ‘green’]; }
Parent.prototype.getName = function () { console.log(this.name) }
function Child (name, age) { // 继承属性,第二次调用 Parent Parent.call(this, name); this.age = age; }
// 继承方法,构建原型链,第一次调用 Parent Child.prototype = new Parent(); // 重写 Child.prototype 的 constructor 属性,指向自己的构造函数 Child Child.prototype.constructor = Child;
var child1 = new Child(‘kevin’, ‘18’); child1.colors.push(‘black’); console.log(child1.name); // kevin console.log(child1.age); // 18 console.log(child1.colors); // [“red”, “blue”, “green”, “black”]
var child2 = new Child(‘daisy’, ‘20’); console.log(child2.name); // daisy console.log(child2.age); // 20 console.log(child2.colors); // [“red”, “blue”, “green”]
同样,我们可以把 Child 的实例打印出来看看:<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/1105483/1626395543569-838b6b16-a814-4afa-a108-c4bf222ef055.png#clientId=u1a8eaaff-804f-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=236&id=ua90f1e17&margin=%5Bobject%20Object%5D&name=image.png&originHeight=236&originWidth=712&originalType=binary&ratio=1&rotation=0&showTitle=false&size=27121&status=done&style=none&taskId=u1f87f2ac-1d19-4891-997c-8d0458beb96&title=&width=712)
<a name="wBomx"></a>
# 4. 原型式继承
利用一个空对象作为中介,将某个对象直接赋值给空对象构造函数的原型。
缺点:
1. 包含引用类型的属性值始终都会共享相应的值,跟原型链继承一样。
2. 无法传递参数
```javascript
function createObj(o) {
function F(){}
F.prototype = o;
return new F();
}
Object.create() 对传入其中的对象执行了一次浅复制,将构造函数F的原型直接指向传入的对象。
var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = Object.create(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");
var yetAnotherPerson = object(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");
console.log(person.friends); // "Shelby,Court,Van,Rob,Barbie"
5. 寄生式继承
在原型式继承的基础上,增强对象,返回构造函数。
缺点(同原型式继承)
function createObj (o) {
var clone = Object.create(o);
// 函数的主要作用是为构造函数新增属性和方法,以增强函数
clone.sayName = function () {
console.log('hi');
}
return clone;
}
var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
var p1 = createObj(person);
p1.sayHi(); // "hi"
6. 寄生组合式继承
结合借用构造函数传递参数和寄生模式实现继承,这种方式的高效率体现它只调用了一次 SuperType 构造函数,并且因此避免了在 SuperType.prototype 上面创建不必要的、多余的属性。与此同时,原型链还能保持不变;因此,还能够正常使用 instanceof 和 isPrototypeOf。开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。
function inheritPrototype(subType, superType){
// 创建对象,创建父类原型的一个副本
var prototype = Object.create(superType.prototype);
// 增强对象,弥补因重写原型而失去的默认的constructor 属性
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);
}
var instance1 = new SubType("xyc", 23);
var instance2 = new SubType("lxy", 23);
instance1.colors.push("2"); // ["red", "blue", "green", "2"]
instance1.colors.push("3"); // ["red", "blue", "green", "3"]