Javascript 6大继承 最优是寄生组合继承
原型链继承
原型链继承是比较常见的继承方式之一,其中涉及的构造函数、原型和实例,三者之间存在着三角的关系,即每一个构造函数都有一个原型对象「prototype」,原型对象又包含一个指向构造函数的指针「constructor」,然后实例则包含一个隐式原型对象的指针「__ proto __ 称为隐式原型我想做一层区分而已帮助理解,抱歉因为Markdown语法关系,__ proto __ 存在空格」。
示例代码:
function Parent(){
this.name='parent'
this.list=[1,2,3,4]
}
function Child(){
this.type='child'
}
Child.prototype=new Parent()
const c=new Child()
const c1=new Child()
c1.list.push(5)
console.log(c.name) //parent
console.log(c.list) //[1,2,3,4,5]
console.log(c1.list) //[1,2,3,4,5]WARNING
无法实现多继承
构造函数继承 借助 call/apply
function Parent(name){
this.name=name
this.list=[1,2,3,4]
}
Parent.prototype.getName=function() {
return this.name;
}
function Child(name){
this.type='child'
Parent.call(this,name)
}
const c=new Child('c')
console.log(c.name) //c
console.log(c.getName()) //报错除了 Child2 的属性 type 之外,也继承了 Parent2 的属性 name。这样写的时候子类虽然能够拿到父类的属性值,解决了第一种继承方式的弊端,但问题是,只能继承父类的实例属性和方法,不能继承原型属性或者方法。
WARNING
无法继承父类的prototype
上面的两种继承方式各有优缺点,那么结合二者的优点,于是就产生了下面这种组合的继承方式。
组合继承
function Parent(name) {
this.name = name
this.list = [1, 2, 3, 4]
}
Parent.prototype.getName = function () {
return this.name
}
function Child(name) {
this.type = 'child'
// 第2次调用 Parent()
Parent.call(this, name)
}
// 第一次调用 Parent()
Child.prototype = new Parent()
Child.prototype.constructor = Child //构造函数指向自己
var s3 = new Child('s3');
var s4 = new Child('s4');
s3.list.push(5)
console.log(s3.list, s4.list); // 不互相影响
console.log(s3.getName()); // 正常输出'c'
console.log(s4.getName()); // 正常输出'c'又增加了一个新问题:通过注释我们可以看到 Parent3 执行了两次,第一次是改变Child3 的 prototype 的时候,第二次是通过 call 方法调用 Parent3 的时候,那么 Parent3 多构造一次就多进行了一次性能开销,这是我们不愿看到的。 这里还有一个比较细节的问题是第二次调用的Parent3,出现了属性在不同层级重复,Parent3的age也会在实例第一层对象上面,拥有这个“多余的”属性也按照原型链的规则,没什么问题。但在某些情况下会造成错误,例如删除实例上的age属性后,实际上还能访问到,此时获取到的是原型上的属性。
原型式继承(利用Object.create)
let parent4={
name:'parent4',
friends:["p1", "p2", "p3"],
getName:function(){
retrun 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()); // tom === tom ===> tom
console.log(person5.name); // parent4
console.log(person4.friends); // ["p1", "p2", "p3",'jerry','lucy']
console.log(person5.friends); // ["p1", "p2", "p3",'jerry','lucy']WARNING
这种继承方式弊端是拷贝对象引用,这种可能会导致对象被修改。
寄生式继承
使用原型式继承可以获得一份目标对象的浅拷贝,然后利用这个浅拷贝的能力再进行增强,添加一些方法,这样的继承方式就叫作寄生式继承。 虽然其优缺点和原型式继承一样,但是对于普通对象的继承方式来说,寄生式继承相比于原型式继承,还是在父类基础上添加了更多的方法。
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());寄生组合式继承
function Parent() {
this.name = 'parent6';
this.play = [1, 2, 3];
}
function Child() {
Parent.call(this)
this.friends = 'child6';
}
function clone(parent, child) {
child.prototype = Object.create(parent.prototype)
child.prototype.constructor = child;
}
clone(Parent, Child)
Child6.prototype.getFriends = function () {
return this.friends;
}
let person6 = new Child6();
console.log(person6);
console.log(person6.getName());
console.log(person6.getFriends());
指南