开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第27天,点击查看活动详情
首先来说继承的概念,继承源于构造函数,基于构造函数实现不同的继承方式,继承方式 有很多种,各有优缺点,我们在实际业务中可能很少自己去写这种继承,但是并不代表可以不了解,面试的时候也会被经常问到这个,所以接下来咱们就看看常见的这继承都有哪几种~
原型继承
- 核心: 让子类的原型指向父类的实例
- 代码: 子类.prototype = new 父类
- 优点: 父类构造函数体内的属性和原型上的方法都可以实现继承
- 缺点:
- 继承下来的属性不在自己身上,在自己的原型上
- 一个构造函数的实例,需要再两个地方传递参数
- 所有子类的实例继承于父类的属性都一样
- 子类和父类方法的地址一样,没有分离
function Dad(m){
}
function Box(a){
this.a=a;
console.log(a);
}
Dad.prototype.play=function(){
console.log("play");
console.log(this.a,"---play");
}
Box.prototype = new Dad()
借用继承(call或apply继承)
- 核心: 把父类构造函数当作普通函数使用,改变其this指向,指向子类的实例对象
- 代码: 在子类构造函数体内书写 父类.call(this,参数,参数...)
- 优点:
- 子类所有继承下来的属性都在自己身上
- 子类所有参数都在一个地方传递
- 子类所有实例给继承下来的属性赋值 互不影响
- 缺点: 父类的原型上的方法没有继承
function Box(a){
this.a=a;
console.log(a);
}
function Ball(a){
Box.apply(this,arguments);
}
组合继承
- 核心: 把 原型继承 和 借用继承 一起使用
- 优点:
- 属性和方法都能继承下来
- 属性在自己身上,每一个子类实例都可以自己操作
- 缺点: 属性在子类的原型上还有一套,没有使用价值,但是占位
ES6 类继承
- 核心: 就是组合继承的升级版,子类原型上继承下来那套属性做了处理
- 语法:
- class 子类 extends 父类 { }
- 继承父类原型上的方法
- 在子类 constructor 内 书写super(实参)
- 继承父类的属性
- class 子类 extends 父类 { }
- 注意: extends 和 super 必须都写, 在 constructor 内 super 必须写在最前面
class Box1{
constructor(a){
// this.a=a;
// console.log(a);
}
play(){
console.log(this.a,"---play");
}
}
class Ball1 extends Box1{
constructor(a){
super(a)
}
}
var a=new Box1();
var b=new Ball1();
console.log(a)
console.log(b);
寄生式组合继承
Function.prototype.extends=function(superClass){
// this 继承的子类
function F(){}
// 这里来使用一个函数,来继承父类的原型,避免直接使用 父类实例,使得父类在 new 的时候,去执行一次父类内部的代码,而后面还需要使用 apply 借用继承去执行一次父类,再去继承父类的属性和方法, 这样就避免了 执行两次父类,继承两次父类的属性和方法.
F.prototype=superClass.prototype;
//通过if判断函数原型的构造器是否是自己,不是自己,重新设置会自己,来继承超类
if(superClass.prototype.constructor!==superClass){
Object.defineProperty(superClass.prototype,"constructor",{
value:superClass
})
}
// 先把子类的原型存一下,以继承了父类的原型以后,让子类原来的原型恢复
var proto=this.prototype;
// 原型继承 继承父类原型上的属性和方法
this.prototype=new F();
// 获取子类原来的原型中所有的key,通过遍历key,得到原型中对应的这个key的描述对象,将子类原有的原型中的属性恢复
var names=Reflect.ownKeys(proto);
for(var i=0;i<names.length;i++){
var desc=Object.getOwnPropertyDescriptor(proto,names[i]);
Object.defineProperty(this.prototype,names[i],desc);
}
// 借用继承 ---> 给实例化的原型上添加一个 super 的方法,子类当中直接使用实例对象上的 super方法执行, 相当于这里执行, arguments就是子类中构造函数的 arguments,通过参数传入这里,this就是实例化对象,执行父类的函数体内的代码.改变this指向,传入参数 ---> 继承父类的属性和方法
this.prototype.super=function(arguments){
superClass.apply(this,arguments);
}
//向子类的原型上添加一个属性,这个属性继承了父类的原型,使得子类能够通过改变this指向的方式从写父类原型上的方法
this.prototype.supers=superClass.prototype;
}
// Box 是父类
Ball.extends(Box);
function Ball(a){
this.super(arguments);
}
Ball.prototype.run=function(){
console.log("run")
}
Ball.prototype.play=function(){
// 需要在函数里使用 this, 就需要直接找到父类原型上的方法,改变this指向传参
this.supers.play.apply(this,arguments)
console.log("aaa")
}
var b=new Ball(5);
Ball.__proto__.num = 10
b.play();
后记
其实除了 ES6
的 extends
继承, 其他的继承方法都是我们或者社区自己创造的方法,也不是官方的命名,我们大致知晓他们的种类和实践方式就可以了,实际运用极少~