我们先写个父类和子类
function Parent(){
this.name = {name:'jicheng'};
}
Parent.prototype.pro = function(){
console.log('prototype');
}
function Child(){};
一、原型链继承
- 核心:把父类私有+公有的属性,都作为子类公有
Child.prototype = new Parent();//父类的实例作为子类的原型
Child.prototype.constructor = Child;//手动指定constructor指向
let c = new Child();
console.log(c.name,c.pro())
- 子类的实例通过__proto__ 找到所属类Child的原型prototype,即父类的一个实例 该实例拥有Parent的私有属性--实例上的属性,
- 特点:
- 简单、易实现
- 实例既是子类的实例,也是父类的实例
- 父类的公私属性都能拿到。
- 无法实现多继承,不能向父类传参
二、call继承 (构造继承)
- 核心:把父类私有作为子类私有
通过使用call、apply方法可以在新创建的对象上执行构造函数,用父类的构造函数来增加子类的实例,等于是复制父类的实例属性给子类(没用到原型)
function Child(){
Parent.call(this);
}
- 特点:
- 简单明了
- 实例只是子类的实例,并不是父类的实例
- 可以实现多继承,可以传参
- 不能继承原型上的属性
- 每个子类都有父类函数的副本,影响性能
三、实例继承
- 核心:把父类公有和私有属性作为子类私有 在子类中返回父类的实例
function Child(name){
var p = new Parent();
return p;
}
- 特点:
- 实例是父类的实例,不是子类的实例
- 不支持多继承
四、拷贝继承
- 核心:把父类公有和私有属性作为子类公有
在子类中遍历父类的实例,然后分别赋值给子类prototype
function Child(name){
var p = new Parent();
for(let key in p){//for in 可以把p的__proto__上的属性也可以遍历到
Child.prototype[key] = p[key]
}
}
- 特点:
- 效率低,占内存高
- 可以实现多继承
- 无法继承父类不可枚举的方法(for in)
五、组合继承
- 核心:原型继承+构造继承,把父类私有作为子类私有,父类公有作为子类公有
在子类中添加父类的实例并改变this指向,然后把父类的实例赋值给子类的原型 注意恢复子类Child的原型prototype的constructor指向
function Child(name){
Parent.call(this);
}
Child.prototype = new Parent();//实际上把父类私有也带过来了,但是子类实例访问的时候首先访问子类的私有属性
Child.prototype.constructor = Child
- 特点:
- 弥补了构造继承的缺陷,可以继承实例属性/方法,也可以继承原型属性/方法
- 既是子类的实例,也是父类的实例
- 不存在引用属性共享问题
- 可传参 函数可复用
- 调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)
六、寄生组合继承
- 核心:把父类私有作为子类私有,父类公有作为子类公有
在子类中添加父类的实例并改变this指向
//实例属性
function Child(name){
Parent.call(this);
}
//公有属性
(function(){
let M = function(){};
M.prototype = Parent.prototype;
Child.prototype = new M();
Child.prototype.constructor = Child;
})()
其实上述共有属性的继承方式也就是模仿Object.create()的原理
所以也可以写成:
Child.prototype = Object.create(Parent.prototype,{constructor:{value:Child}})
- 特点:完美
七、类的继承
- 核心:父类公有作为子类公有,父类私有作为子类私有
es6方法extends,以及属性constructor super等
class Child extends Parent{
constructor(){
//子类必须在constructor方法中调用super方法,否则新建实例时会报错,
//因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工。如果不调用super方法,子类就得不到this对象。
super()
}
}
- 特点:
- 内部super()相当于Parent.call(this)
- 外部extends相当于Object.create(Child.prototype,Parent.prototype,{constructor:{value:Child}})
- 可以传参
- 补充:
- __prpto__本质上是一个内部属性,而不是一个正式的对外的 API,目前,所有浏览器(包括 IE11 )都部署了这个属性。
- Child.prototype=Object.create(Parent.prototype,{constructor:{value:Child}})是es5方法,原理是创建一个超类接收父类原型上的属性方法,最后返回实例,注意在第二个参数描述器中设置constructor指向
- Object.setPrototypeOf(Child.prototype, Parent.prototype) 是ES6正式推荐的设置原型对象的方法。
总结
- 原型继承:父类公+私=>子类公有 子类的原型指向父类的实例
- Child.prorotype = new Parent()
- Child.prototype.constructor = Child
- 实例继承:父类公+私=>子类私有
- function Child(){ return new Parent()} 子类函数内返回父类的实例
- 构造继承:父类私有=>子类私有
- function Child(){ Parent.call(this)} 子类函数内执行父类函数
- 拷贝继承:父类公+私=>子类公有 遍历父类函数,逐个赋值给子类原型
- for(let key in Parent){ Child.prototype[key] = Parent[key]}
- Child.prototype.constructor = Child
- 组合继承 构造+原型
- 寄生组合继承 构造+Object.create
- 类的继承 extends + super
实际应用中还是要根据不同的应用场景选择不同的继承方式
欢迎批评指正!