继承设计的初衷:
1.方法可以复用,子实例不用重复创建方法(创建方法开销大),可直接用父实例中的(或者说原型中的)。
2.子实例里面的所有属性,都可以通过构造函数参数传值的方式设置,且子实例间属性不会相互干扰。
1.原型链
构造函数、原型、实例关系图
原型链关系图
注意:我们每声明一个函数,它本省都有一个自己的原型(Object),只是我们修改了该函数的原型指向。因此你用父实例作原型时,里面是没有constructor属性的,会根据原型链搜索到第一个有constructor值的原型上。
原型链的问题:
违背初衷2
1.原型中(父实例)包含引用类型的值时,子实例对该属性的操作会相互干扰。首先能共享父实例中的属性本是一个好事,当属性为基本类型时,子实例们都可以访问到,而又无法修改它(sub.color = 'red'这种操作并不会修改父实例中的color值,而是在sub实例中新建了一个color属性并赋值red了)。但如果color是引用类型时,例如数组,你用push或者pop去操作了,那个每个实例的color值都会改变。
2.子类型实例不能给父类型构造函数传参,也就是说虽然你可以在子构造中用this设置每个子实例的属性,但父实例的属性都只能通过原型链访问到,无法在子实例创建的时候就设置自己的与父实例同名的属性值(你只有在创建完sub实例后,再用sub.color=red设置自己的color属性)。
2.借用构造函数
其实说起来很简单,在子构造函数中调用父构造函数,只是通过call或者apply去修改了this。使得所有父实例拥有的属性都被定义到了子实例上。
解决了原型链的问题:1.引用类型属性相互干扰问题。2.无法通过子构造传参设置父实例属性初始值问题。
借用构造函数的问题:
违背初衷1
所有父实例中的方法,都在子实例中被重新定义了一遍。方法复用无从谈起,其实原型链继承很好的做到了方法复用。
3.组合式继承
说白了就是原型链和借用构造相结合的方式,把原型属性和方法全定义到原型上,不要定义到各级的父实例上。父实例只定义实例属性即可。组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优点,成为 JavaScript 中最常用的继承模式。
组合继承的问题: 组合继承其实调用了两次父类构造函数(创建父实例一次,子构造函数中又用了一次), 造成了不必要的消耗,不过这个缺陷其实已经可以接受了。
4.原型式继承
用一个方法(如下),将父实例作为参数传入,方法内部创建一个空的子构造方法,原型指向父实例,同时返回一个用该子构造创建的子实例。也就是说字构造子实例都是空白的,全用父实例的方法和属性。ES5的Object.create()方法在只有第一个参数时,功能与下面的objectCopy方法相同(create的第二个参数格式和defiendProperty相同,为新对象定义额外的属性)
function objectCopy(obj) {
function Fun() { };
Fun.prototype = obj;
return new Fun()
}
原型式继承的问题:
违背初衷2'
1.父类的引用会被所有子类所共享
2.子类实例不能向父类传参
注: 其实这么看原型式继承不是和原型链一样嘛,只是你子构造函数中不给自己的子实例加私有属性了,然后把继承的整个操作用一个方法概括简化了,别的啥也没干啊。
5.寄生式继承
加强版原型式继承,给子实例加了新的方法,再返回。
寄生式继承的问题:
原型式继承的问题感觉一个没解决。
违背初衷1' 方法复用无从谈起,每个子实例都定义了新方法。
6.寄生组合式继承
利用寄生式继承,对组合式继承的改良。组合式继承唯一的不好不是两次创建父实例嘛。利用寄生式继承(objectCopy方法),创建空的构造和仅有方法无属性的空的实例都直接指向父构造的原型(而不是又一个父实例,省去了创造父实例的过程)。然后真正的子构造方法原型指向空实例,同时还可以将空实例的construct指向真正的子构造。
function objectCopy(obj) {
function Fun() { };
Fun.prototype = obj;
return new Fun();
}
function inheritPrototype(child, parent) {
let prototype = objectCopy(parent.prototype); // 创建对象
prototype.constructor = child; // 增强对象
Child.prototype = prototype; // 赋值对象
}
function Parent(name) {
this.name = name;
this.friends = ["rose", "lily", "tom"]
}
Parent.prototype.sayName = function () {
console.log(this.name);
}
function Child(name, age) {
Parent.call(this, name);
this.age = age;
}
inheritPrototype(Child, Parent);
Child.prototype.sayAge = function () {
console.log(this.age);
}
let child1 = new Child("yhd", 23);
child1.sayAge(); // 23
child1.sayName(); // yhd
child1.friends.push("jack");
console.log(child1.friends); // ["rose", "lily", "tom", "jack"]
let child2 = new Child("yl", 22)
child2.sayAge(); // 22
child2.sayName(); // yl
console.log(child2.friends); // ["rose", "lily", "tom"]
本质上是用空实例(有方法无属性)代替了之前的父实例位置,省了一次父实例创建过程以及使得原型链更干净,各级有方法但无属性,原型属性放在顶层的原型上。
集合了所有优势,几乎无缺点