原型链继承
通过原型继承多个引用类型的属性和方法。
一个构造函数的原型是另一个构造函数的实例。
function SuperType() {
this.property = true;
}
SuperType.prototype.getSuperValue = function() {
return this.property;
};
function SubType() {
this.subproperty = false;
}
// 继承SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function() {
return this.subproperty;
};
let instance = new SubType();
console.log(instance.getSuperValue()); // true
原型与实例的关系可以通过两种方式来确定。
第一种方式是使用 instanceof
操作符,如果一个实例的原型链中出现过相应的构造函数,则 instanceof
返回 true
。
console.log(instance instanceof Object); // true
console.log(instance instanceof SuperType); // true
console.log(instance instanceof SubType); // true
第二种方式是使用 isPrototypeOf()
方法。原型链中的每个原型都可以调用这个方法。
console.log(Object.protptype.isPrototypeOf(instance)); // true
console.log(SuperType.prototype.isPrototypeOf(instance)); // true
console.log(SubType.prototype.isPrototypeOf(instance)); // true
instanceof 与 isPrototypeOf 的区别:
isPrototypeOf 用来检测一个对象是否存在于另一个对象的原型链上,如果存在就返回 true ,否则就返回 false 。
instanceof 用于检测构造函数的 prototyp 属性是否出现在某个实例对象的原型链上。
原型链继承的问题:
- 原型中包含的引用值会在所有实例间共享。 ```javascript function SuperType() { this.colors = [“red”, “blue”, “green”]; }
function SubType() {}
// 继承SuperType SubType.prototype = new SuperType();
let instance1 = new SubType(); instance1.colors.push(“black”); console.log(instance1.colors); // “red,blue,green,black”
let instance2 = new SubType(); console.log(instance2.colors); // “red,blue,green,black”
- 子类型在实例化时不能给父类型的构造函数传参。
<a name="edoUa"></a>
# 借用构造函数继承
在子类构造函数中调用父类构造函数。因为毕竟函数就是在特定上下文中执行代码的简单对象,所以可以使用 `apply` 和 `call` 方法以新创建的对象为上下文执行构造函数。
```javascript
function SuperType() {
this.colors = ["red", "blue", "green"];
}
function SubType() {
// 继承SuperType
SuperType.call(this);
}
let instance1 = new SubType();
instance1.colors.push("black");
console.log(instance1.colors); // "red, blue, green, black"
let instance2 = new SubType();
console.log(instance2.colors); // "red, blue, green"
优点:
- 可以向父类传递参数
- 解决了原型中包含引用类型值被所有实例共享的问题
缺点:
- 无法继承原型中的方法
- 实例并不是父类的实例,只是子类的实例
- 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能
function Animation(name, age) {
this.name = name;
this.age = age;
}
Animation.prototype.eat = function() {
console.log(this.name);
}
function Cat(name, age, sex, score) {
Animation.call(this, name, age);
this.sex = sex;
this.score = score;
}
var c1 = new Cat("tom", 18, "男", 100)
c1.cat() // TypeError
console.log(c1 instanceof Animation) // false
console.log(c1 instanceof Cat) // true
组合继承
综合了原型链和构造函数,将两者的优点集中了起来。基本的思想是使用原型链继承原型上的属性和方法,而通过借用构造函数继承实例属性。这样既可以把方法定义在原型上以实现重用,又可以让每个实例都有自己的属性。 ```javascript 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(“Nicholas”, 29); instance1.colors.push(“black”); console.log(instance1.colors); // “red,blue,green,black” instance1.sayName(); // “Nicholas” instance1.sayAge(); // 29
let instance2 = new SubType(“Greg”, 27); console.log(instance2.colors); // “red, blue, green” instance2.sayName(); // “Greg” instance2.sayAge(); // 27
特点:
- 可以继承父类原型上的属性,可以传参,函数可复用。
- 每个新实例引入的构造函数属性是私有的。
缺点:
- 调用了两次父类构造函数(耗内存)(一次是在创建子类型原型的时候,另一次是在子类型构造函数内部),子类的构造函数会代替原型上的那个父类构造函数。
<a name="iRWUj"></a>
# 原型式继承
即使不自定义类型也可以通过原型实现对象之间的信息共享。
```javascript
function object(o) {
function F() {};
F.prototype = o;
return new F();
}
ES6 通过增加 Object.create()
方法将原型式继承的概念规范化了。
原型式继承非常适合不需要单独创建构造函数,但仍然需要在对象间共享信息的场合。但要记住,属性中包含的引用值始终会在相关对象间共享,跟使用原型模式是一样的。
寄生式继承
寄生式继承是与原型式继承紧密相关的一种思路。寄生式继承的思路与寄生构造函数和工厂模式类似,即创建一个实现继承的函数,以某种方式增强对象,然后返回这个对象。
function createAnother(original) {
let clone = object(original); // 通过调用函数创建一个新对象
clone.sayHi = function() { // 以某种方式增强这个对象
console.log("hi");
};
return clone; // 返回这个对象
}
let person = {
name: "Nicholas",
fridends: ["Shelby", "Court", "Van"]
};
let anotherPerson = createAnother(person);
anotherPerson.sayHi(); // 'hi'
在考虑对象而不是自定义类型和构造函数的情况下,寄生式继承也是一种有用的模式。
缺点:
- 使用寄生式继承来为对象添加函数,会由于不能做到函数复用而效率低下
- 同原型链实现继承一样,包含引用类型值得属性会被所有实例共享。
- 没用到原型,无法复用。
寄生组合式继承
本质上,子类原型最终是要包含超类对象的所有实例属性,子类构造函数只要在执行时重写自己的原型就行了。
寄生式组合继承通过构造函数继承属性,但是用混合式原型链继承方法,基本思路是不通过父类构造函数给子类原型赋值,而是取得父类原型的一个副本,说到底就是使用寄生式继承来继承父类原型,然后将返回的新对象赋值给子类原型。寄生式组合继承的基本模式如下所示: ```javascript 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); };
优点:
- 只调用了一次超类构造函数,效率更高。
- 避免在 SuperType.prototype 上面创建不必要得、多余得属性,与此同时,原型链还能保持不变。
<a name="C4HsF"></a>
# ES5继承 VS ES6继承
1. ES5 的继承实质上是先创建子类的实例对象,然后再将父类的方法添加到 this 上(`Parent.apply(this)` )
2. ES6 的继承实质上是先创建父类的实例对象 this(所以必须先调用父类的 `super()` 方法),然后再用子类的构造函数修改 this
3. ES5 的继承通过原型或构造函数机制来实现
4. ES6 通过 class 关键字定义类,里面有构造方法,类之间通过 extends 关键字实现继承。子类必须在 constructor 方法中调用 super 方法,否则新建实例报错。因为子类没有自己的 this 对象,而是继承了父类的 this 对象,然后对其进行加工。如果不调用 super 方法,子类得不到 this 对象
<a name="msfwW"></a>
## ES5 继承
```javascript
function parent (a, b) {
this.a = a;
this.b = b;
}
function child(c) {
this.c = c
};
// 通过子级去继承父级
parent.call(child, 1, 2)
而去看 call 的底层方法可知,继承的过程是通过 prototype 属性
child.prototype = new parent(1, 2);
由此可知, ES5 继承的实质是先创建了子类元素 child 的实例对象,然后再把父类元素 parent 的原型对象中的属性赋值给子类元素 child 的实例对象里面,从而实现继承。
ES6 中的继承
ES6 中的继承是基于 class 类之间继承的。通过关键字 extends 实现。
通过 super 实例化调用父类。
class parent {
constructor(a, b) {
this.a = a;
this.b = b;
}
parentMethods() {
return this.a + this.b;
}
}
class child extends parent {
constructor(a, b, c) {
super(a, b);
this.c = c;
}
childMethods() {
return this.c + ',' + super.parentMethods();
}
}
const point = new child(1, 2, 3);
alert(point.childMethods());
参考链接: