原型链继承
通过原型继承多个引用类型的属性和方法。
一个构造函数的原型是另一个构造函数的实例。
function SuperType() {this.property = true;}SuperType.prototype.getSuperValue = function() {return this.property;};function SubType() {this.subproperty = false;}// 继承SuperTypeSubType.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); // trueconsole.log(instance instanceof SuperType); // trueconsole.log(instance instanceof SubType); // true
第二种方式是使用 isPrototypeOf() 方法。原型链中的每个原型都可以调用这个方法。
console.log(Object.protptype.isPrototypeOf(instance)); // trueconsole.log(SuperType.prototype.isPrototypeOf(instance)); // trueconsole.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` 方法以新创建的对象为上下文执行构造函数。```javascriptfunction SuperType() {this.colors = ["red", "blue", "green"];}function SubType() {// 继承SuperTypeSuperType.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() // TypeErrorconsole.log(c1 instanceof Animation) // falseconsole.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># 原型式继承即使不自定义类型也可以通过原型实现对象之间的信息共享。```javascriptfunction 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()` 方法),然后再用子类的构造函数修改 this3. ES5 的继承通过原型或构造函数机制来实现4. ES6 通过 class 关键字定义类,里面有构造方法,类之间通过 extends 关键字实现继承。子类必须在 constructor 方法中调用 super 方法,否则新建实例报错。因为子类没有自己的 this 对象,而是继承了父类的 this 对象,然后对其进行加工。如果不调用 super 方法,子类得不到 this 对象<a name="msfwW"></a>## ES5 继承```javascriptfunction 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());
参考链接:
