通过 ES5 实现 ES6 继承

1,441 阅读3分钟

这是我参与更文挑战的第3天,活动详情查看:更文挑战

题目

将下面这个 ES6 的继承用 ES5 代码进行实现

class Parent {
    static author = 'LvLin';
    constructor(name) {
        this.name = name;
    }
    getName() {
        return this.name;
    }
    static getAuthor() {
        return Parent.author;
    }
}

class Child extends Parent {
    constructor(name, age) {
        super(name);
        this.age = age;
    }
    sayHello() {
        console.log('Hello, I am ' + super.getName());
    }
    static printAuthor() {
        console.log(super.getAuthor());
    }
}

实现

原型链实现继承(extends)

所谓「继承」,是面向对象软件技术当中的一个概念。如果一个类 B 继承自一个类 A,那么称 B 为 A 的 子类,称 A 为 B 的父类。继承使子类具有父类的各种属性和方法,从而不需要再编写相同的代码,但是子类可以追加新的属性和方法,也可以覆盖父类的属性和方法。

各个语言都有属于自己的继承机制,JavaScript 的继承是通过原型链实现的。将需要被继承的属性、方法都定义在构造函数的原型 prototype 里。当实例对象在自己身上找不到相关的属性或方法时,会去原型链上寻找父类的属性或方法。深入了解可以参看这篇文章

let child = new Child()
child.__proto__ === Child.prototype     // true
child.__proto__.__proto__ === Parent.prototype     // true
child.getName === Parent.prototype.getName     // true

暂时不考虑 ES6 类继承中 static、super 部分的实现, ES5 代码如下:

function Parent (name) {
    this.name = name;
}

Parent.prototype.getName = function() {
    return this.name;
}
Parent.prototype.constructor = Parent;

function Child (name, age) {
    //... 存在 super 关键字,后续实现
}

Child.prototype = new Parent();  // 这一步可以优化
Child.prototype.constructor = Child;

其中 Child.prototype = new Parent() 这一步,我们只希望让 Child.prototype.__proto__ 指向 Parent.prototype,不希望调用 Parent 构造方法,所以可以适当优化一下:

var F = function () {};
F.prototype = Parent.prototype;
Child.prototype = new F();

static 实现

静态(static)属性、静态方法,都是定义在 Class 本身的属性,只能通过类调用,供所有实例共享,不被继承,所以不能定义在原型上,定义在构造函数本身,如下:

Parent.getAuthor = 'LvLin';
Parent.getAuthor = function() {
    return Parent.author;
}

super 实现

在 ES6 中,super 既可以是函数,也可以是对象,存在两种情况:

  1. 作为函数调用时,代表父类的构造函数。
  2. 作为对象时,在普通方法中,指向父类的原型对象(需要注意此时 this 的指向);在静态方法中,指向父类。

所以 ES6 类继承中的 super 可以改写成以下 ES5 代码:

function Child (name, age) {
    Parent.call(this, name);
    this.age = age;
}

Child.prototype.sayHello = function() {
    console.log('Hello, I am ' + Parent.prototype.getName().call(this));
}

Child.printAuthor = function() {
    console.log(Parent.getAuthor());
}

最终结果

结合以上分析,最终改写的 ES5 代码如下所示:

function Parent (name) {
    this.name = name;
}

Parent.prototype.getName = function() {
    return this.name;
}
Parent.prototype.constructor = Parent;
Parent.author = 'LvLin';
Parent.getAuthor = function() {
    return Parent.author;
}

function Child (name, age) {
    Parent.call(this, name);
    this.age = age;
}

var F = function () {};
F.prototype = Parent.prototype;
Child.prototype = new F();
Child.prototype.constructor = Child;

Child.prototype.sayHello = function() {
    console.log('Hello, I am ' + Parent.prototype.getName().call(this));
}
Child.printAuthor = function() {
    console.log(Parent.getAuthor());
}

测试验证一下:

let child = new Child('LvLin', 18)
child.sayHello()   //  Hello, I am LvLin
child.getName()    // "LvLin"
Child.printAuthor()    // LvLin

最后需要注意的是,ES6 的类继承,其实存在两条原型链,类之间也是存在继承关系,尝试一下:

Child.__proto__ === Parent  // true

参考

Class 的基本语法Class 的继承,by 阮一峰

JavaScript深入之从原型到原型链JavaScript深入之继承的多种方式和优缺点,by 冴羽