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
關(guān)于Class的語法推薦看這里:es6.ruanyifeng.com/#docs/class
ES6 的class可以看作只是一個語法糖,它的絕大部分功能,ES5 都可以做到,新的class寫法只是讓對象原型的寫法更加清晰、更像面向?qū)ο缶幊痰恼Z法而已。但是它們還是有區(qū)別的。
區(qū)別:
- 類必須使用new調(diào)用,否則會報錯。ES的構(gòu)造函數(shù)是可以當(dāng)成普通函數(shù)使用的
- 類的內(nèi)部所有定義的方法,都是不可枚舉的。(包括內(nèi)部定義的靜態(tài)方法)
- 類的靜態(tài)方法也可以被子類繼承
- 可以繼承原生構(gòu)造函數(shù)
- ES5 是先新建子類的實例對象this,再將父類的屬性添加到子類上,由于父類的內(nèi)部屬性無法獲取,導(dǎo)致無法繼承原生的構(gòu)造函數(shù)。
- ES6 允許繼承原生構(gòu)造函數(shù)定義子類,因為 ES6 是先新建父類的實例對象this,然后再用子類的構(gòu)造函數(shù)修飾this,使得父類的所有行為都可以繼承
使用ES5模擬實現(xiàn)ES6的class
根據(jù)上面的區(qū)別,我們一步步的看。
1. new操作符檢查函數(shù)
解決問題:
- 類必須使用new調(diào)用,否則會報錯。ES的構(gòu)造函數(shù)是可以當(dāng)成普通函數(shù)使用的
function _checkType (obj, constructor) {
if (!(obj instanceof constructor)) {
throw new TypeError('Cannot call a class as a function')
}
}
2. 內(nèi)部方法不可枚舉
解決問題:
- 類的內(nèi)部所有定義的方法,都是不可枚舉的。(包括內(nèi)部定義的靜態(tài)方法)
// 修改構(gòu)造函數(shù)描述符
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)
}
}
// 構(gòu)造class
// constructor 表示類對應(yīng)的constructor對象
// protoDesc 表示class內(nèi)部定義的方法
// staticDesc 表示class內(nèi)部定義的靜態(tài)方法
function _createClass (constructor, protoDesc, staticDesc) {
protoDesc && defineProperties(constructor.prototype, protoDesc)
staticDesc && defineProperties(constructor, staticDesc)
return constructor
}
3. 真正的創(chuàng)建class
const Foo = function () {
function Foo(name) {
_checkType(this, Foo) // 先檢查是不是new調(diào)用的
this.name = name
}
_createClass (Foo, [ // 表示在class內(nèi)部定義的方法
{
key: 'say',
value: function () {
console.log(this.name)
}
}
], [ // 表示在class內(nèi)部定義的靜態(tài)方法
{
key: 'say',
value: function () {
console.log('static say')
console.log(this.name)
}
}
])
return Foo
}()
到這里class實現(xiàn)完成,驗證一下。
- 先直接調(diào)用一下Foo(),結(jié)果為:

image.png
- 使用new操作符,生成一個對象
const foo = new Foo('aaa')

image.png
- 打印一下在原型鏈上定義的方法

image.png
可見say方法是不可枚舉的。
- 打印一下靜態(tài)方法

image.png
可見靜態(tài)方法say是不可枚舉的。
4. 實現(xiàn)原型鏈繼承和靜態(tài)方法繼承,并考慮到可以繼承null的情況
解決問題:
- 類的靜態(tài)方法也可以被子類繼承
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
解決的問題:
- 可以繼承原生構(gòu)造函數(shù)
- ES5 是先新建子類的實例對象this,再將父類的屬性添加到子類上,由于父類的內(nèi)部屬性無法獲取,導(dǎo)致無法繼承原生的構(gòu)造函數(shù)。
- ES6 允許繼承原生構(gòu)造函數(shù)定義子類,因為 ES6 是先新建父類的實例對象this,然后再用子類的構(gòu)造函數(shù)修飾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. 創(chuàng)建子類class
const Child = function (_Parent) {
_inherits(Child, _Parent) // 繼承父類原型上的屬性及靜態(tài)方法的繼承
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實現(xiàn)完成。驗證一下。
- 打印一下Child.say()

image.png
Child并沒有在自身定義靜態(tài)方法,但是它的父類有定義。繼承成功。
- 構(gòu)造一個繼承原生構(gòu)造函數(shù)的子類
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
繼承成功。
相關(guān)文章
關(guān)注公眾號「前端開發(fā)博客」,回復(fù)1024,領(lǐng)取前端資料包