使用ES5實現ES6的Class

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寫法只是讓對象原型的寫法更加清晰、更像面向對象編程的語法而已。可是它們仍是有區別的。es6

區別:express

  • 類必須使用new調用,不然會報錯。ES的構造函數是能夠當成普通函數使用的
  • 類的內部全部定義的方法,都是不可枚舉的。(包括內部定義的靜態方法)
  • 類的靜態方法也能夠被子類繼承
  • 能夠繼承原生構造函數編程

    • ES5 是先新建子類的實例對象this,再將父類的屬性添加到子類上,因爲父類的內部屬性沒法獲取,致使沒法繼承原生的構造函數。
    • ES6 容許繼承原生構造函數定義子類,由於 ES6 是先新建父類的實例對象this,而後再用子類的構造函數修飾this,使得父類的全部行爲均可以繼承

使用ES5模擬實現ES6的class

根據上面的區別,咱們一步步的看。函數

1. new操做符檢查函數

解決問題:post

  • 類必須使用new調用,不然會報錯。ES的構造函數是能夠當成普通函數使用的
function _checkType (obj, constructor) {
    if (!(obj instanceof constructor)) {
        throw new TypeError('Cannot call a class as a function')
    }
}

2. 內部方法不可枚舉

解決問題:this

  • 類的內部全部定義的方法,都是不可枚舉的。(包括內部定義的靜態方法)
// 修改構造函數描述符
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實現完成,驗證一下。spa

  • 先直接調用一下Foo(),結果爲:

  • 使用new操做符,生成一個對象
const foo = new Foo('aaa')

  • 打印一下在原型鏈上定義的方法

可見say方法是不可枚舉的。prototype

  • 打印一下靜態方法

可見靜態方法say是不可枚舉的。3d

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()

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)

繼承成功。

相關文章

  1. ES2020 中 Javascript 10 個你應該知道的新功能
  2. 重溫ES6核心概念和基本用法
  3. JavaScript 對象:咱們真的須要模擬類嗎?
原文: https://juejin.im/post/684490...

最後,歡迎關注個人公衆號:前端開發博客,每日分享新鮮技術文章。

相關文章
相關標籤/搜索