美文网首页前端日报
使用ES5实现ES6的Class

使用ES5实现ES6的Class

作者: 前端开发博客 | 来源:发表于2020-11-05 22:45 被阅读0次

    ES5的寄生组合式继承

    function parent (age) {
        this.age = age
    }
    
    parent.prototype.say = function () {
        console.log(this.age)
    }
    
    function sub (age, value) {
        parent.call(this, age)
        this.value = value
    }
    
    sub.prototype = Object.create(parent.prototype, {
        constructor: {
            value: sub,
            enumerable: false,
            writable: true,
            configurable: true
        }
    })
    

    ES6的Class

    关于Class的语法推荐看这里:es6.ruanyifeng.com/#docs/class

    ES6 的class可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。但是它们还是有区别的。

    区别:

    • 类必须使用new调用,否则会报错。ES的构造函数是可以当成普通函数使用的
    • 类的内部所有定义的方法,都是不可枚举的。(包括内部定义的静态方法)
    • 类的静态方法也可以被子类继承
    • 可以继承原生构造函数
      • ES5 是先新建子类的实例对象this,再将父类的属性添加到子类上,由于父类的内部属性无法获取,导致无法继承原生的构造函数。
      • ES6 允许继承原生构造函数定义子类,因为 ES6 是先新建父类的实例对象this,然后再用子类的构造函数修饰this,使得父类的所有行为都可以继承

    使用ES5模拟实现ES6的class

    根据上面的区别,我们一步步的看。

    1. new操作符检查函数

    解决问题:

    • 类必须使用new调用,否则会报错。ES的构造函数是可以当成普通函数使用的
    function _checkType (obj, constructor) {
        if (!(obj instanceof constructor)) {
            throw new TypeError('Cannot call a class as a function')
        }
    }
    

    2. 内部方法不可枚举

    解决问题:

    • 类的内部所有定义的方法,都是不可枚举的。(包括内部定义的静态方法)
    // 修改构造函数描述符
    function defineProperties (target, descriptors) {
        for (let descriptor of descriptors) {
            descriptor.enumerable = descriptor.enumerable || false
    
            descriptor.configurable = true
            if ('value' in descriptor) {
                descriptor.writable = true
            }
    
            Object.defineProperty(target, descriptor.key, descriptor)
        }
    }
    
    // 构造class
    // constructor 表示类对应的constructor对象
    // protoDesc 表示class内部定义的方法
    // staticDesc 表示class内部定义的静态方法
    function _createClass (constructor, protoDesc, staticDesc) {
        protoDesc && defineProperties(constructor.prototype, protoDesc)
        staticDesc && defineProperties(constructor, staticDesc)
        return constructor
    }
    

    3. 真正的创建class

    const Foo = function () {
        function Foo(name) {
            _checkType(this, Foo) // 先检查是不是new调用的
    
            this.name = name
        }
    
        _createClass (Foo, [ // 表示在class内部定义的方法
            {
                key: 'say',
                value: function () {
                    console.log(this.name)
                }
            }
        ], [ // 表示在class内部定义的静态方法
            {
                key: 'say',
                value: function () {
                    console.log('static say')
                    console.log(this.name)
                }
            }
        ])
    
        return Foo
    }()
    

    到这里class实现完成,验证一下。

    • 先直接调用一下Foo(),结果为:
    image.png
    • 使用new操作符,生成一个对象
    const foo = new Foo('aaa')
    
    image.png
    • 打印一下在原型链上定义的方法
    image.png

    可见say方法是不可枚举的。

    • 打印一下静态方法
    image.png

    可见静态方法say是不可枚举的。

    4. 实现原型链继承和静态方法继承,并考虑到可以继承null的情况

    解决问题:

    • 类的静态方法也可以被子类继承
    function _inherits(subClass, superClass) {
        if (typeof superClass !== 'function' && superClass !== null) {
            throw new TypeError('Super expression must either be null or a function, not' + typeof superClass)
        }
        subClass.prototype = Object.create(superClass && superClass.prototype, {
            constructor: {
                value: subClass,
                enumerable: false,
                writable: true,
                configurable: true
            }
        })
    
        if (superClass) {
            Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass
        }
    }
    

    5. 使用父类的实例对象this

    解决的问题:

    • 可以继承原生构造函数
      • ES5 是先新建子类的实例对象this,再将父类的属性添加到子类上,由于父类的内部属性无法获取,导致无法继承原生的构造函数。
      • ES6 允许继承原生构造函数定义子类,因为 ES6 是先新建父类的实例对象this,然后再用子类的构造函数修饰this,使得父类的所有行为都可以继承
    // 返回父类的this;若为null,则返回自身
    function _possibleConstructorReturn(self, call) {
        if (!self) {
            throw new ReferenceError("this hasn't been initialised - super() hasn't been called")
        }
        return call && (typeof call === 'object' || typeof call === 'function') ? call : self
    }
    

    6. 创建子类class

    const Child = function (_Parent) {
        _inherits(Child, _Parent) // 继承父类原型上的属性及静态方法的继承
    
        function Child(name, age) {
            _checkType(this, Child)
    
            // 先使用父类实例对象this,再返回
            const _this = _possibleConstructorReturn(this, (Child.__proto__ || Object.getPrototypeOf(Child)).call(this, name))
            _this.age = age
            return _this
        }
        return Child
    }(Foo)
    

    子类class实现完成。验证一下。

    • 打印一下Child.say()
    image.png

    Child并没有在自身定义静态方法,但是它的父类有定义。继承成功。

    • 构造一个继承原生构造函数的子类
    const Child = function (_Parent) {
        _inherits(Child, _Parent)
    
        function Child(name, age) {
            _checkType(this, Child)
    
            const _this = _possibleConstructorReturn(this, (Child.__proto__ || Object.getPrototypeOf(Child)).call(this, name))
            _this.age = age
            return _this
        }
        return Child
    }(Array)
    
    const c = new Child('bbb', 12)
    
    image.png

    继承成功。

    相关文章

    1. ES2020 中 Javascript 10 个你应该知道的新功能
    2. 重温ES6核心概念和基本用法
    3. JavaScript 对象:我们真的需要模拟类吗?

    关注公众号「前端开发博客」,回复1024,领取前端资料包

    原文:https://juejin.im/post/6844903886034042893

    相关文章

      网友评论

        本文标题:使用ES5实现ES6的Class

        本文链接:https://www.haomeiwen.com/subject/rpczvktx.html