這篇文章主要介紹了關於ES6 Class 繼承與super的介紹,有著一定的參考價值,現在分享給大家,有需要的朋友可以參考一下
#class 可以extends 自另一個class。這是一個不錯的語法,技術上是基於原型繼承。
要繼承一個對象,需要在 {..}
前面指定 extends
和父對象。
這個Rabbit
繼承自Animal
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
|
如你所見,如你所想,extend
關鍵字實際上是在Rabbit.prototype
新增[Prototype]]
,引用到Animal.prototype
。
所以現在 rabbit
既可以存取它自己的方法,也可以存取 Animal
的方法。
extends
後可跟表達式Class 語法的 `extends' 後接的不限於指定一個類,更可以是表達式。
例如一個產生父類別的函數:
1 2 3 4 5 6 7 8 9 10 11 |
|
範例中,class User
繼承了 f('Hello')傳回的結果。
對於高階程式模式,當我們使用的類別是根據許多條件使用函數來產生時,這很有用。
現在讓我們進入下一步,重寫一個方法。到目前為止,Rabbit
從 Animal
繼承了 stop
方法,this.speed = 0
。
如果我們在Rabbit
中指定了自己的stop
,那麼會被優先使用:
1 2 3 4 5 |
|
......但通常我們不想完全取代父方法,而是在父方法的基礎上調整或擴展其功能。我們進行一些操作,讓它之前/之後或在過程中呼叫父方法。
Class 為此提供 super
關鍵字。
使用 super.method(...)
呼叫父方法。
使用 super(...)
呼叫父建構函式(僅在 constructor 函式中)。
例如,讓兔子在stop
時自動隱藏:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
|
現在,Rabbit
的stop
方法透過super.stop()
呼叫父類別的方法。
super
如如在 arrow-functions 一章中提到,箭頭函數沒有 super
。
它會從外部函數中取得 super
。例如:
1 2 3 4 5 |
|
箭頭函數中的 super
與 stop()
中的相同,所以它按預期工作。如果我們在這裡用普通函數,便會報錯:
1 2 |
|
#對於建構子來說,這有點棘手 tricky。
直到現在,Rabbit
都沒有自己的 constructor
。
Till now, Rabbit
did not have its own constructor
.
根據規範,如果一個類別擴展了另一個類別並且沒有constructor
,那麼會自動產生如下constructor
:
1 2 3 4 5 6 7 8 |
|
我們可以看到,它呼叫了父constructor
傳遞所有參數。如果我們不自己寫構造函數,就會發生這種情況。
現在我們將一個自訂建構函式加入到 Rabbit
中。除了name
,我們還會設定 earLength
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
|
哎呦出錯了!現在我們不能生成兔子了,為什麼呢?
簡單來說:繼承類別中的建構子必須呼叫 super(...)
,(!)並且在使用 this
之前執行它。
...但為什麼?這是什麼情況?嗯...這個要求看起來確實很奇怪。
現在我們探討細節,讓你真正理解其中緣由 ——
在JavaScript中,繼承了其他類別的建構子比較特殊。在繼承類別中,對應的建構子被標記為特殊的內部屬性 [[ConstructorKind]]:「derived」
。
差異在於:
當一個普通的建構子執行時,它會建立一個空物件作為 this,然後繼續運作。
但是當衍生的建構函式運行時,與上面說的不同,它指望父建構函式來完成這項工作。
所以如果我們正在建構我們自己的建構函數,那麼我們必須呼叫super
,否則具有this
的物件將不被創建,並報錯。
對於Rabbit
來說,我們需要在使用this
之前呼叫super()
,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
|
讓我們再深入理解super
的底層實現,我們會看到一些有趣的事情。
首先要說的是,以我們迄今為止學到的知識來看,實現 super 是不可能的。
那么思考一下,这是什么原理?当一个对象方法运行时,它将当前对象作为 this
。如果我们调用 super.method()
,那么如何检索 method
?很容易想到,我们需要从当前对象的原型中取出 method
。从技术上讲,我们(或JavaScript引擎)可以做到这一点吗?
也许我们可以从 this
的 [[Prototype]] 中获得方法,就像 this .__ proto __.method
一样?不幸的是,这是行不通的。
让我们试一试,简单起见,我们不使用 class 了,直接使用普通对象。
在这里,rabbit.eat()
调用父对象的 animal.eat()
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
在 (*)
这一行,我们从原型(animal
)中取出 eat
,并以当前对象的上下文中调用它。请注意,.call(this)
在这里很重要,因为只写 this .__ proto __.eat()
的话 eat
的调用对象将会是 animal
,而不是当前对象。
以上代码的 alert
是正确的。
但是现在让我们再添加一个对象到原型链中,就要出事了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
噢,完蛋!调用 longEar.eat()
报错了!
这原因一眼可能看不透,但如果我们跟踪 longEar.eat()
调用,大概就知道为什么了。在 (*)
和 (**)
两行中, this
的值是当前对象(longEar
)。重点来了:所有方法都将当前对象作为 this
,而不是原型或其他东西。
因此,在两行 (*)
和 (**)
中,this.__ proto__
的值都是 rabbit
。他们都调用了 rabbit.eat
,于是就这么无限循环下去。
情况如图:
1.在 longEar.eat()
里面,(**)
行中调用了 rabbit.eat
,并且this = longEar
。
1 2 3 4 5 6 |
|
2.然后在rabbit.eat
的 (*)
行中,我们希望传到原型链的下一层,但是 this = longEar
,所以 this .__ proto __.eat
又是 rabbit.eat
!
1 2 3 4 5 6 |
|
...因此 rabbit.eat
在无尽循环调动,无法进入下一层。
这个问题不能简单使用 this
解决。
[[HomeObject]]
为了提供解决方案,JavaScript 为函数添加了一个特殊的内部属性:[[HomeObject]]
。
当函数被指定为类或对象方法时,其 [[HomeObject]]
属性为该对象。
这实际上违反了 unbind 函数的思想,因为方法记住了它们的对象。并且 [[HomeObject]]
不能被改变,所以这是永久 bind(绑定)。所以在 JavaScript 这是一个很大的变化。
但是这种改变是安全的。 [[HomeObject]]
仅用于在 super
中获取下一层原型。所以它不会破坏兼容性。
让我们来看看它是如何在 super
中运作的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
每个方法都会在内部 [[HomeObject]]
属性中记住它的对象。然后 super
使用它来解析原型。
在类和普通对象中定义的方法中都定义了 [[HomeObject]]
,但是对于对象,必须使用:method()
而不是 "method: function()"
。
在下面的例子中,使用非方法语法(non-method syntax)进行比较。这么做没有设置 [[HomeObject]]
属性,继承也不起作用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
class
语法也支持静态属性的继承。
例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
|
现在我们可以调用 Rabbit.compare
,假设继承的 Animal.compare
将被调用。
它是如何工作的?再次使用原型。正如你猜到的那样,extends 同样给 Rabbit
提供了引用到 Animal
的 [Prototype]
。
所以,Rabbit
函数现在继承 Animal
函数。Animal
自带引用到 Function.prototype
的 [[Prototype]]
(因为它不 extend
其他类)。
看看这里:
1 2 3 4 5 6 7 8 9 10 11 |
|
这样 Rabbit
可以访问 Animal
的所有静态方法。
请注意,内置类没有静态 [[Prototype]]
引用。例如,Object
具有 Object.defineProperty
,Object.keys
等方法,但 Array
,Date
不会继承它们。
Date
和 Object
的结构:
Date
和 Object
之间毫无关联,他们独立存在,不过 Date.prototype
继承于 Object.prototype
,仅此而已。
造成这个情况是因为 JavaScript 在设计初期没有考虑使用 class 语法和继承静态方法。
Array,Map 等内置类也可以扩展。
举个例子,PowerArray
继承自原生 Array
:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
请注意一件非常有趣的事情。像 filter
,map
和其他内置方法 - 返回新的继承类型的对象。他们依靠 constructor
属性来做到这一点。
在上面的例子中,
1 |
|
所以当调用 arr.filter()
时,它自动创建新的结果数组,就像 new PowerArray
一样,于是我们可以继续使用 PowerArray 的方法。
我们甚至可以自定义这种行为。如果存在静态 getter Symbol.species
,返回新建对象使用的 constructor。
下面的例子中,由于 Symbol.species
的存在,map
,filter
等内置方法将返回普通的数组:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
我们可以在其他 key 使用 Symbol.species
,可以用于剥离结果值中的无用方法,或是增加其他方法。
以上就是本文的全部内容,希望对大家的学习有所帮助,更多相关内容请关注PHP中文网!
相关推荐:
以上是ES6 Class 繼承與 super的介紹的詳細內容。更多資訊請關注PHP中文網其他相關文章!