一、原型链继承
原型链继承就是将父类的实例赋给子类的原型对象
function parent(name){
this.name = 'jack';
}
parent.prototype.getName = function(){
console.log(this.name)
}
function child(){}
child.prototype = new parent();
const child1 = new child()
child1.getName();//jack
console.log(child.name);//jack
复制代码
优点: 可以直接继承父类的属性和方法,这里的属性继承是通过__proto__找到的,不是实例本身拥有的。
缺点: 1.不能向父类传参; 2.引用属性会被所有实例共享。
就像下面这种情况,属性被实例共享导致引用类型易被修改:
function parent(){
this.name = ['tom','jack']
}
parent.prototype.getName = function(){
console.log(this.name)
}
function child(){}
child.prototype = new parent();
const child1 = new child();
const child2 = new child();
child1.name.push("jery")
console.log(child1.name,child2.name)//['tom','jack','jery']
复制代码
child的constructor指向parent,所以我们使用的时候应该手动修改指向:
child.prototype.constructor = child;
二、构造函数继承
构造函数继承就是在子类调用父类来实现继承
function parent(name){
this.name = name;
}
function child(name){
parent.call(this,name);
}
const child1 = new child('jack');
console.log(child1.name)//jack
复制代码
优点:可以继承父类的属性(这里的属性继承是实例本身自己的),可以向父类传参,引用类型不会被所有实例共享,constructor还是指向自己child。
缺点:不能继承父类原型上的方法,方法都在构造函数中定义,每次创建实例都会创建一遍方法。
function parent(name){
this.name = name;
this.arr = ['111']
}
function child(name){
parent.call(this,name);
}
const jack = new child('jack');
const tom = new child('tom')
jack.arr.push('222');
console.log(jack.arr,tom.arr)//(2) ["111", "222"] ["111"]
复制代码
所以上面两个继承方式各有优劣,一个能继承父类的方法,但是属性被实例共享,易修改,另一个方式虽然能很好的继承父类的属性(这里的属性是自己的),但是父类原型上的方法被多次创建。于是看看下面这位:
三、组合继承
如果结合原型继承和构造函数继承,这样就可以结合其优点,摒弃其缺点了,这就是组合继承
function parent(name){
this.name = name;
this.arr = ['111']
}
parent.prototype.getName = function(){
console.log(this.name)
}
function child(name){
parent.call(this,name)
}
child.prototype = new parent();
const jack = new child('jack');
const tom = new child('tom');
jack.arr.push('222');
jack.getName();//jack
console.log(jack.arr,tom.arr)//2) ["111", "222"] ["111"]
复制代码
优点: 可以向父类传参,可以继承父类的属性(子类会有从父类继承的属性,__proto__上基本属性是undefined,引用属性是有的,看下图)和原型上的方法,引用属性不会被共享:
缺点: 一个实例会实例化父类两次,parent.call(this,name)调用一次,child.prototype = new parent()调用一次,constructor指向了parent。
三、原型继承
function create(o){
function Fun(){};
Fun.prototype = o;
return new Fun();
}
复制代码
原型继承其实就是es5的object.create()的实现方式,请看下面的代码:
const person = {
name: 'name',
arr: ['111','222']
}
const p1 = create(person);
const p2 = create(person);
p1.name = 'p1';
p1.arr.push('aaa');
console.log(p1.name,p2.name);//p1,name
console.log(p1.arr,p2.arr);//(3) ["111", "222", "aaa"] (3) ["111", "222", "aaa"]
复制代码
缺点:会共享引用属性
修改p1.name的值,p2.name的值并未发生改变,并不是因为p1和p2有独立的 name 值,而是 因为p1.name='p1',给person1添加了 name 值,并非修改了原型上的name,值。p1,p2的constructor属性是指向object的,p1.name是在父类原型上找到的,p2.name=‘name’,继承的属性在__proto__。
四、寄生式继承
创建一个仅用于封装继承过程的函数,该函数在内部以某种形式来做增强对象,最后返回对象。
function createObj (o) {
var clone = object.create(o);
clone.sayName = function () {
console.log('hi');
}
return clone;
}
复制代码
缺点:跟构造函数模式一样,每次创建对象都会创建一遍方法。
五、寄生组合式继承
继承终极大boss!!寄生组合式继承,结合了以上的优点,来看下代码 组合继承的缺点就是会调用两次父类,如何避免调用两次呢?能不能直接让子类的prototype访问到父类的prototype?当然可以!!看下面的代码
function parent(name){
this.name = name;
this.arr = ['111']
}
parent.prototype.getName = function(){
console.log(this.name)
}
function child(name){
parent.call(this,name)
}
//注意!!核心代码(解决调用两次父类)
function Fun(){}
Fun.prototype = parent.prototype;
child.prototype = new Fun();
const c1 = new child('c1');
const c2 = new child('c2');
c1.arr.push('ccc');
c1.name = 'name';
c1.getName();//name
c2.getName();//c2
console.log(c1,c2);//child {name: "name", arr: Array(2)} child {name: "c2", arr: Array(1)}
复制代码
缺点: 子类的constructor指向指向了父类,应该修复一下,可以改写如下 可以改写上面的关键步骤:
function create(o){
function F(){}
F.prototype = o;
return new F();
}
function prototype(child,parent){
const p = create(parent.prototype);
p.constructor = child;
child.prototype = p;
}
复制代码