如何用ES5去实现ES6的类语法

1,297 阅读3分钟

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

前言

这是一个高频面试题,应该很多同学都被问到过,比如我旁边的同学被问了不下两次←_←。在考虑如何用ES5实现以前,先来简单回顾下ES6的类语法与ES5构造函数的写法到底有啥区别,具体ES6 class部分请移步阮大

类语法与构造函数语法区别

  • 函数声明可以提升,类声明不能提升
  • 类声明的代码默认运行在严格模式下
  • 在类中,所有方法都是不可枚举的
  • 在类中,只能通过new关键词去调用类的构造函数
  • 类中通过static关键词定义的属性和方法只能通过类本身去访问,不能通过类实例去访问。
  • 在类中不可以修改类名

下边是一个ES6 class继承的例子:

ES6实现

class FatherFun{
    constructor(val) {
        this.fatherVal = val;
    }


    getFatherVal() {
        console.log('父类属性值:',this.fatherVal);
    }

    static staticMethod() {
        console.log('这是一个父类静态方法');
    }
}

class SonFun extends FatherFun{
    constructor(val, sonVal) {
        super(val);
        this.sonVal = sonVal;
    }
    getSonVal() {
        console.log('子类属性值:', this.sonVal);
    }

    static staticSonMethod() {
        console.log('这是一个子类静态方法');
    }
}

在ES6 class语法没有出现以前,我们实现继承最常用的方式是通过构造函数+原型链来实现的,但是ES5是没有static实现的。static要求不能在实例访问,只能类本身访问。我们知道JS中一切皆对象,如果我们把static属性当作是对象中的一个key,那是不是就可以实现只能构造函数本身去访问,实例无法访问呢~

ES5实现(第一版)

function FatherFun(val) {
    this.fatherVal = val;
}

FatherFun.prototype.getFatherVal = function() {
    console.log(this.fatherVal);
}

FatherFun.staticMethod = function() {
    console.log('这是一个父类静态方法');
}



//构造函数+原型链实现继承
function SonFun(fatherVal, sonVal) {
    FatherFun.call(this, fatherVal);
    this.sonVal = sonVal;
}
SonFun.prototype = new FatherFun();
SonFun.prototype.constructor = SonFun;

SonFun.prototype.getSonVal = function() {
    console.log(this.sonVal);
}

SonFun.staticSonMethod = function() {
    console.log('这是一个子类静态方法');
}

但是对于类语法的一些特殊用法我们要怎么去实现呢??

  1. 类声明不能提升这块,我们可以考虑用let或者const来声明构造函数。
  2. 加入use strict来表明严格模式下运行
  3. 只通过new关键词调用这块,我们可以考虑使用new.target来判断
  4. 所有方法不能枚举,可以考虑用Object.defineProperty去修改enumerable
  5. 类的名称只在类中是常量,不可修改,外部是可以修改的。可以考虑在类内部重复声明类名为const

ES5实现(第二版)

先考虑实现父类FatherFun:

let FatherFun = (function () {
    'use strict';

    const FatherFun = function(val) {
        if(typeof new.target === 'undefined') {
            throw new Error('必须通过new关键词调用构造函数');
        }
        this.fatherVal = val;
    }
    Object.defineProperty(FatherFun.prototype, 'getFatherVal', {
        value: function() {
            if(typeof new.target !== 'undefined') {
                throw new Error('必须通过new关键词调用构造函数');
            }
            console.log(this.fatherVal);
        },
        enumerable: false,
        writable: true,
        configurable: true
    })

    FatherFun.staticProp = 'static property';
    FatherFun.staticMethod = function() {
        console.log('这是一个父类静态方法');
    }

    return FatherFun;
})()

直接通过父类本身去访问父类的静态方法和属性

FatherFun.staticMethod();    //这是一个父类静态方法

通过父类实例去访问父类属性和方法

let f = new FatherFun('我是父类实例');
console.log(f1.fatherVal);  //father
f1.getFatherVal();          //父类属性值: 我是父类实例
console.log(f.staticProp);   //undefined
f.staticMethod();    //Uncaught TypeError: f.staticMethod is not a function

问题来了,ES5中我们一般是用构造函数+原型链的方式去实现继承,但是由于类是只能通过new关键字来调用,所以构造函数的方式是无法使用,就只能考虑其他方式来实现继承了,如只用原型链。