JS之实现继承的N种方法

分享一下我一年前端自学路上的学习笔记。若有侵权,请联系我,我立即删除或标明出处。


//通过构造函数实现继承
function Parent() {
  this.name = "Parent1";
}
Parent1.prototype.sex = "男";
function Child() {
  Parent1.call(this);  //apply也可以
  this.type = "Child";
}
console.log(new Child().name);      //Parent1  
console.log(new Child().sex);       //undefined

小结:上述的通过构造函数实现继承,只能实现部分继承,无法继承父类原型对象,并没有真正的实现继承。

//通过原型链实现继承
function Parent() {
    this.name = "Parent";
    this.play = [1, 2, 3];
}
Parent.prototype.sex = "男";
function Child() {
    this.type = "Child";
}
Child.prototype = new Parent;
var s1 = new Child();
var s2 = new Child();
console.log(s1.sex, s2.sex);        //男,男
console.log(s1.play, s2.play);      //[1, 2, 3],[1, 2, 3]
s1.play.push(4);
console.log(s1.play, s2.play)       //[1, 2, 3, 4],[1, 2, 3, 4]

小结:上述通过原型链来实现继承,解决了通过构造函数实现继承的缺点,使之能继承父类的原型对象,但是实例s1和s2共用原型链上的原型对象,两个并不隔离,修改会相互干预。

//通过构造函数和原型链组合实现继承
function Parent() {
    this.name = "Parent";
    this.play = [1, 2, 3];
}
function Child() {
    Parent.call(this);
    this.type = "Child";
}
Child.prototype = new Parent();
var s1 = new Child();
var s2 = new Child();
console.log(s1.play, s2.play);      //[1, 2, 3],[1, 2, 3]
s1.play.push(4);
console.log(s1.play, s2.play);      //[1, 2, 3, 4],[1, 2, 3]

小结:上述的通过组合继承方式,解决了上面两种方式继承的缺点,既实现对父类原型对象的继承,而且实例相互隔离,修改互不干预。但是父类的构造被执行了两次,第一次是Child.prototype = new Parent(),第二次是实例化时Parent.call(this),这是没有必要的。我们下面进行优化。

//组合第一次优化
function Parent() {
    this.name = "Parent";
    this.play = [1, 2, 3];
}
function Child() {
    Parent.call(this);
    this.type = "Child";
}
Child.prototype = Parent.prototype;     //这是没优化之前的代码Child.prototype = new Parent();
var s1 = new Child();
var s2 = new Child();
console.log(s1.play, s2.play);      //[1, 2, 3],[1, 2, 3]
s1.play.push(4);
console.log(s1.play, s2.play);      //[1, 2, 3, 4],[1, 2, 3]

小结:上述是组合继承的第一次优化,我们将父类的原型对象直接赋给子类的原型对象。这样就减少了一次父类构造函数运行次数。但是!为什么叫第一次优化,因为这次优化并不是完美的。其实有个问题我没有提出,接着第一次优化的代码,请看下面代码的执行结果。

//组合继承的隐藏问题,接组合继承方式第一次优化代码继续看
console.log(s1 instanceof Child);       //true
console.log(s2 instanceof Parent)       //true
console.log(s1.constructor.name);       //Parent

小结:上述代码可以看出我们无法判断实例s1是由Child直接实例化还有由Parent间接实例化,所以我们使用s1.constructor.name来确认。但结果表明s1是由父类构造实例化出来,而并非子类。所以我们来进行最后一次优化。

//通过构造函数和原型链组合实现继承的最后一次优化
function Parent() {
    this.name = "Parent";
    this.play = [1, 2, 3];
}
function Child() {
    Parent.call(this);
    this.type = "Child";
}
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;        
var s1 = new Child();
var s2 = new Child();
console.log(s1.constructor.name);       //Child

小结:上述代码为最后一次组合优化。放弃了Child.prototype = Parent.prototype而使用一个中间对象将父类和子类相关联Child.prototype = Object.create(Parent.prototype),之后再修改Child.prototype.constructor = Child,从而解决实例是由父类构造函数实例化出来的问题。或许你会问为什么不在第一次优化上直接修改Child.prototype.constructor?那你就仔细思考下我们为什么要使用中间对象Object.create()。好了,除了上面这么多实现继承方式外,还有吗?看就继续看喽。

//ES6实现继承
class Parent {
    constructor(name, id) {
        this.name = name;
        this.id = id;
    }
    say() {
        console.log(this.name)
    }
}
class Child extends Parent {
    constructor(name, id, age) {
        super(name, id);
        this.age = age;
    }
}
let s1 = new Child('hi', 18, 18)
console.log(s1, s1.say());

小结:上面是ES6的继承。ES6中Class通过extends关键字,使用super()方法调用父类的constrctor(),从而实现继承,是不是比ES5方便很多。


上面就是实现继承的N种方式,希望对大家有所帮助。特别注意一点,ES5继承和ES6继承是不同的。这里不细说了,网上资料很多。
简单的说,ES5实质是先创造子类的实例对象this,然后再将父类的的方法添加到上面。ES6则是先创造父类的实例对象this,然后再用子类的构造函数修改this.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值