复制代码
可以看到最后打印的 child 在控制台显示,除了 Child1 的属性 type 之外,也继承了 Parent1 的属性 name。这样写的时候子类虽然能够拿到父类的属性值,解决了第一种继承方式的弊端。
问题:父类原型对象中一旦存在父类之前自己定义的方法,那么子类将无法继承这些方法。
3、组合继承
这种方式结合了前两种继承方式的优缺点,结合起来的继承。
function Parent3 () {
this.name = 'parent3';
this.play = [1, 2, 3];
}
Parent3.prototype.getName = function () {
return this.name;
}
function Child3() {
// 第二次调用 Parent3()
Parent3.call(this);
this.type = 'child3';
}
// 第一次调用 Parent3()
Child3.prototype = new Parent3();
// 手动挂上构造器,指向自己的构造函数
Child3.prototype.constructor = Child3;
var s3 = new Child3();
var s4 = new Child3();
s3.play.push(4);
console.log(s3.play, s4.play); // 不互相影响
console.log(s3.getName()); // 正常输出’parent3’
console.log(s4.getName()); // 正常输出’parent3’
复制代码
问题:通过注释我们可以看到 Parent3 执行了两次,第一次是改变Child3 的 prototype 的时候,第二次是通过 call 方法调用 Parent3 的时候,那么 Parent3 多构造一次就多进行了一次性能开销,这是我们不愿看到的。
4、原型式继承
ES5 里面的 Object.create 方法,这个方法接收两个参数:一是用作新对象原型的对象、二是为新对象定义额外属性的对象(可选参数)。
let parent4 = {
name: "parent4",
friends: ["p1", "p2", "p3"],
getName: function() {
return this.name;
}
};
let person4 = Object.create(parent4);
person4.name = “tom”;
person4.friends.push(“jerry”);
let person5 = Object.create(parent4);
person5.friends.push(“lucy”);
console.log(person4.name);//tom
console.log(person4.name === person4.getName());//true
console.log(person5.name);//parent4
console.log(person4.friends);//[“p1”,“p2”,“p3”,“jerry”,“lucy”]
console.log(person5.friends);//[“p1”,“p2”,“p3”,“jerry”,“lucy”]
复制代码
通过 Object.create 这个方法可以实现普通对象的继承,不仅仅能继承属性,同样也可以继承 getName 的方法。
第一个结果“tom”,比较容易理解,person4 继承了 parent4 的 name 属性,但是在这个基础上又进行了自定义。
第二个是继承过来的 getName 方法检查自己的 name 是否和属性里面的值一样,答案是 true。
第三个结果“parent4”也比较容易理解,person5 继承了 parent4 的 name 属性,没有进行覆盖,因此输出父对象的属性。
最后两个输出结果是一样的,其实 Object.create 方法是可以为一些对象实现浅拷贝的。
问题:多个实例的引用类型属性指向相同的内存,存在篡改的可能。
5、寄生继承
使用原型式继承可以获得一份目标对象的浅拷贝,然后利用这个浅拷贝的能力再进行增强,添加一些方法,这样的继承方式就叫作寄生式继承。
虽然其优缺点和原型式继承一样,但是对于普通对象的继承方式来说,寄生式继承相比于原型式继承,还是在父类基础上添加了更多的方法。
let parent5 = {
name: "parent5",
friends: ["p1", "p2", "p3"],
getName: function() {
return this.name;
}
};
function clone(original) {
let clone = Object.create(original);
clone.getFriends = function() {
return this.friends;
};
return clone;
}
let person5 = clone(parent5);
console.log(person5.getName());
console.log(person5.getFriends());
复制代码
person5 是通过寄生式继承生成的实例,它不仅仅有 getName 的方法,而且可以看到它最后也拥有了 getFriends 的方法。person5 通过 clone 的方法,增加了 getFriends 的方法,从而使 person5 这个普通对象在继承过程中又增加了一个方法,这样的继承方式就是寄生式继承。
问题:优缺点和原型式继承一样,但是对于普通对象的继承方式来说,寄生式继承相比于原型式继承,还是在父类基础上添加了更多的方法。
6、寄生组合式继承
结合第四种中提及的继承方式,解决普通对象的继承问题的 Object.create 方法,我们在前面这几种继承方式的优缺点基础上进行改造,得出了寄生组合式的继承方式,这也是所有继承方式里面相对最优的继承方式
function clone (parent, child) {
// 这里改用 Object.create 就可以减少组合继承中多进行一次构造的过程
child.prototype = Object.create(parent.prototype);
child.prototype.constructor = child;
}
function Parent6() {
this.name = 'parent6';
this.play = [1, 2, 3];
}
Parent6.prototype.getName = function () {
return this.name;
}
function Child6() {
Parent6.call(this);
this.friends = 'child5';
}
clone(Parent6, Child6);
Child6.prototype.getFriends = function () {
return this.friends;
}
let person6 = new Child6();
console.log(person6);
console.log(person6.getName());
console.log(person6.getFriends());
复制代码
寄生组合式继承方式,基本可以解决前几种继承方式的缺点,较好地实现了继承想要的结果,同时也减少了构造次数,减少了性能的开销。
总结:
(1)第一种是以原型链的方式来实现继承,但是这种实现方式存在 的缺点是,在包含有引用类型的数据时,会被所有的实例对象所共享, 容易造成修改的混乱。还有就是在创建子类型的时候不能向超类型传 递参数。
(2)第二种方式是使用借用构造函数的方式,这种方式是通过在子 类型的函数中调用超类型的构造函数来实现的,这一种方法解决了不 能向超类型传递参数的缺点,但是它存在的一个问题就是无法实现函 数方法的复用,并且超类型原型定义的方法子类型也没有办法访问到。
(3)第三种方式是组合继承,组合继承是将原型链和借用构造函数 组合起来使用的一种方式。通过借用构造函数的方式来实现类型的属 性的继承,通过将子类型的原型设置为超类型的实例来实现方法的继 承。这种方式解决了上面的两种模式单独使用时的问题,但是由于我 们是以超类型的实例来作为子类型的原型,所以调用了两次超类的构 造函数,造成了子类型的原型中多了很多不必要的属性
(4)第四种方式是原型式继承,原型式继承的主要思路就是基于已 有的对象来创建新的对象,实现的原理是,向函数中传入一个对象, 然后返回一个以这个对象为原型的对象。这种继承的思路主要不是为 了实现创造一种新的类型,只是对某个对象实现一种简单继承,ES5 中定义的 Object.create() 方法就是原型式继承的实现。缺点与原 型链方式相同。
(5)第五种方式是寄生式继承,寄生式继承的思路是创建一个用于 封装继承过程的函数,通过传入一个对象,然后复制一个对象的副本, 然后对象进行扩展,最后返回这个对象。这个扩展的过程就可以理解 是一种继承。这种继承的优点就是对一个简单对象实现继承,如果这 个对象不是自定义类型时。缺点是没有办法实现函数的复用。
(6)第六种方式是寄生式组合继承,组合继承的缺点就是使用超类 型的实例做为子类型的原型,导致添加了不必要的原型属性。寄生式 组合继承的方式是使用超类型的原型的副本来作为子类型的原型,这 样就避免了创建不必要的属性。
上面的继承看见就想吐,而且还有人拷问这些东西,很烦!还好ES6出了class和extends ,我们可以通过class创造类,使用extends实现类的继承。
class Person {
constructor(name) {
this.name = name
}
// 原型方法
// 即 Person.prototype.getName = function() { }
// 下面可以简写为 getName() {…}
getName = function () {
console.log('Person:', this.name)
}
}
class Gamer extends Person {
constructor(name, age) {
// 子类中存在构造函数,则需要在使用“this”之前首先调用 super()。
super(name)
this.age = age
}
}
const asuna = new Gamer(‘Asuna’, 20)
最后
开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】
最后写上我自己一直喜欢的一句名言:
世界上只有一种真正的英雄主义就是在认清生活真相之后仍然热爱它