Description
一、原型链继承
基本思想:重写原型对象,将父类实例赋值给子类原型对象。
原来存在于父类实例中的所有属性和方法,现在也存在于子类原型中了。
function Parent () {
this.property = false;
}
Parent.prototype.getSuperValue = function () {
return this.property;
}
function Child () {
this.subProperty = true;
}
Child.prototype = new Parent();
Child.prototype.getSubValue = function () {
return this.subProperty;
}
var child1 = new Child();
console.log(child1.getSuperValue()) // false
console.log(child1.getSubValue()) // true
原型链: child1.proto -> Child.prototype Child.prototype.proto -> Parent.prototype
缺点:
- 子类原型的属性会被所有子类实例共享,所以引用类型属性的变动会影响所有子类实例
- 创建子类实例时无法向父类的构造函数传参
二、借用构造函数继承
基本思想:在子类构造函数的内部调用父类构造函数
function Parent () {
this.names = ['kevin', 'daisy'];
}
function Child () {
Parent.call(this);
}
var child1 = new Child();
child1.names.push('yayu');
console.log(child1.names); // ["kevin", "daisy", "yayu"]
var child2 = new Child();
console.log(child2.names); // ["kevin", "daisy"]
缺点:
- 方法都在构造函数中定义,无法复用
- 不能继承原型属性和方法
三、组合继承
基本思想:用原型链实现原型属性和方法的继承,借用构造函数实现对实例属性的继承
function Parent (name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
Parent.prototype.getName = function () {
console.log(this.name)
}
function Child (name, age) {
// 创建子类实例时,第二次调用父类构造函数
Parent.call(this, name);
this.age = age;
}
// 第一次调用父类构造函数
Child.prototype = new Parent();
// 重新赋值子类原型之后,需要重写constructor指向自身
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"]
缺点:会调用两次父类构造函数,给子类实例和子类原型重复写入属性和方法。
四、原型式继承
基本思想:将传入对象作为构造函数实例指向的原型,本质是对传入对象进行了一次浅拷贝。
返回构造函数的实例instance。instance.proto === F.prototype === obj;
ES5通过object.reate()规范了原型式继承
function object(obj) {
function F() {}
F.prototype = obj;
return new F();
}
var person = {
name: "nike",
friends: ["a", "b"],
}
var anotherPerson = object(person);
anotherPerson.name = 'Greg';
anotherPerson.friends.push("c");
var yetAnotherPerson = object(person);
yetAnotherPerson.name = 'linda';
yetAnotherPerson.friends.push("d");
console.log(person.friends); // ["a", "b", "c", "d"]
缺点:引用类型的值会被子类实例改变,无法传递参数。
五、寄生式继承
基本思想:类似工厂模式,在原型式继承基础上增强对象,返回构造函数
function createAnother(original) {
// 通过调用 object() 函数创建一个新对象
var clone = object(original);
clone.sayHi = function(){
// 以某种方式来增强对象
alert("hi");
};
return clone; // 返回这个对象
}
缺点:同原型式继承,只是为实例增加属性和方法。
六、寄生组合式继承
基本思路:主要是省略掉子类原型赋值,不必为了指定子类原型而调用父类构造函数,我们所需要的的只是父类原型的副本。
本质:使用寄生式继承来继承父类的原型,再将结果指定给子类的原型
function inheritPrototype(child, parent) {
// 创建父类原型对象的副本
var prototype = Object.create(parent.prototype);
// 弥补因重写原型而丢失的constructor
prototype.constructor = child;
// 新创建的对象赋值给子类原型
child.prototype = prototype;
}
// 父类初始化实例属性和原型属性
function Parent() {
this.name = name;
this.colors = ["red", "blue", "green"];
}
Parent.prototype.sayName = function(){
alert(this.name);
};
// 借用构造函数传递增强子类实例属性(支持传参和避免篡改)
function Child(name, age){
Parent.call(this, name);
this.age = age;
}
// 将父类原型指向子类
inheritPrototype(Child, Parent);
// 新增子类原型属性
Child.prototype.sayAge = function(){
alert(this.age);
}
var instance1 = new Child("xyc", 23);
var instance2 = new Child("lxy", 23);
原型链: instance.proto -> Child.prototype Child.prototype.proto -> Parent.prototype
相比之前的组合继承,只调用了一次父类构造函数,避免了重复属性和方法的创建。
七、ES6继承
通过class创建父类,子类通过extends继承父类
extends 原型链接 B.prototype.proto = A.prototype;
super()子类调用父类构造方法,继承父类属性
class A {
constructor(name) {
this.name = name;
}
}
class B extends A {
constructor() {
// 执行A构造函数,
// super()内部的this指向的是B, 即A.prototype.constructor.call(this)
super();
}
}
ES5继承和ES6继承的区别:
ES5的继承是先new创建子类实例对象,然后再将父类方法添加到this上
ES6的继承不同点在于是先创建父类的实例对象this,然后再用子类的构造函数修改this。所以子类构造函数使用this之前必须先调用super()方法创建this。