摘要:即是由此我們可以輕松偽造一個實例對象可是這是對對象的屬性的修改,和有什么關系靜態方法的繼承少年,可別忘了函數本身也是個對象喲在上面的代碼中,我們使無關對象的指向構造函數的,于是使被判定為的實例。
關于 __proto__ 屬性,MDN 上的解釋是這樣的[1]:
The __proto__ property of Object.prototype is an accessor property (a getter function and a setter function) that exposes the internal [[Prototype]] (either an object or null) of the object through which it is accessed.
即是說,__proto__ 屬性指向了實例對象的原型 Constructor.prototype。那么,這個屬性里隱藏著怎樣的黑魔法呢?
ES6 class 的實現最近看 ECMAScript 6 的 spec,發現了一些有意思的東西,比如 class 章節:
14.5.14 Runtime Semantics: ClassDefinitionEvaluation[2.1]With parameter className.
ClassTail : ClassHeritageopt { ClassBodyopt }
...6.g (for class heritage)
If superclass has a [[FunctionKind]] internal slot whose value is "generator", throw a TypeError exception.
Let protoParent be Get(superclass, "prototype").
ReturnIfAbrupt(protoParent).
If Type(protoParent) is neither Object nor Null, throw a TypeError exception.
Let constructorParent be superclass.
7. Let proto be ObjectCreate(protoParent).
...
12. Let constructorInfo be the result of performing DefineMethod for constructor with arguments proto and constructorParent as the optional functionPrototype argument.
...
16. Perform MakeConstructor(F, false, proto).
...
18. Perform CreateMethodProperty(proto, "constructor", F).
...
這幾行規定了類繼承(class SubClass extends SuperClass {})的行為,除了眾所周知的 SubClass.prototype = Object.create(SuperClass.prototype) 以外,還做了一件有趣的事:Let constructorParent be superclass, proto be ObjectCreate(protoParent), and performing DefineMethod for constructor with arguments proto and constructorParent as the optional functionPrototype argument.
追溯 functionPrototype 變量的去向,發現是這樣的:
14.3.8 Runtime Semantics: DefineMethod[2.2]With parameters object and optional parameter functionPrototype.
...6. Let closure be FunctionCreate(kind, StrictFormalParameters, FunctionBody, scope, strict). If functionPrototype was passed as a parameter then pass its value as the functionPrototype optional argument of FunctionCreate.
...
9.2.5 FunctionCreate (kind, ParameterList, Body, Scope, Strict, prototype)[2.3]The abstract operation FunctionCreate requires the arguments: kind which is one of (Normal, Method, Arrow), a parameter list production specified by ParameterList, a body production specified by Body, a Lexical Environment specified by Scope, a Boolean flag Strict, and optionally, an object prototype.
...4. Let F be FunctionAllocate(prototype, Strict, allocKind).
...
9.2.3 FunctionAllocate (functionPrototype, strict [,functionKind] )[2.4]...
12. Set the [[Prototype]] internal slot of F to functionPrototype.
...
原來 functionPrototype 被用作了 SubClass 的 [[Prototype]] 屬性!
Babel[2] 對繼承的實現如下:
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; }
道理我都懂,可是為什么要這樣做?
[[prototype]] 與原型鏈要檢測一個對象是否是一個構造函數的實例,我們通常會用 O instanceof C 這樣的表達式,在 spec 中,instanceof 運算符這樣被定義:
12.9.4 Runtime Semantics: InstanceofOperator(O, C)[2.5]...
2. Let instOfHandler be GetMethod(C,@@hasInstance).
...
19.2.3.6 Function.prototype[@@hasInstance] ( V )[2.6]Let F be the this value.
Return OrdinaryHasInstance(F, V).
7.3.19 OrdinaryHasInstance (C, O)[2.6]...
4. Let P be Get(C, "prototype").
...
7. Repeat
Let O be O.[[GetPrototypeOf]]().
ReturnIfAbrupt(O).
If O is null, return false.
If SameValue(P, O) is true, return true.
9.1.1 [[GetPrototypeOf]] ( )[2.6]Return the value of the [[Prototype]] internal slot of O.
大致描述如下:instanceof 運算符掉用了 Function.prototype 上的內部方法 @@hasInstance,此方法將 this 對象(即 C)的 prototype 屬性與實例對象 O 的 [[prototype]] 對比,如果后者 [[prototype]] 為 null 則返回 false,如果兩者相等,則返回 true,否則沿原型鏈向上比較,直到得出結果。
即是:
O instanceof C => O.__proto__ === C.prototype ? true: O.__proto__.__proto__ === C.prototype ? true : ...
由此我們可以輕松偽造一個實例對象:
class A { whoami() { return "Instance of A"; } } let a = new A(); let b = {}; Object.setPrototypeOf(b, A.prototype); // b.__proto__ = A.prototype a.whoami() =** b.whoami(); // true b instanceof A; // true
可是這是對對象的 __proto__ 屬性的修改,和 SubClass.__proto__ 有什么關系?
靜態方法的繼承少年,可別忘了 JavaScript 函數本身也是個對象喲!
在上面的代碼中,我們使無關對象 b 的 __proto__ 指向構造函數 A 的 prototype,于是使 b 被判定為 A 的實例。同時,A 的所有原型方法都被 b 所繼承!
換句話說,如果將 SubClass 的 __proto__ 屬性指向 SuperClass,父類上的所有屬性都將被子類繼承!比如:
class A { static whoami() { return "A Constructor!"; } greet() { return "hello world!"; } } function B() {} Object.setPrototypeOf(B, A); B.whoami(); // "A Constructor!"
此時,我們再將 B.prototype 的 __proto__ 屬性指向 A.prototype,即可完成原型方法的繼承:
Object.setPrototypeOf(B.prototype, A.prototype); let b = new B(); b.greet(); // "hello world!" b instanceof B; // true b instanceof A; // true
如此一來,子類就構造完成了!可以開開心心造孩子去了!
惡搞:讓函數 B 成為函數 A 的實例利用 instanceof 運算符的定義,我們還能玩出一些神奇的事,比如:
function A() {}; A.prototype = A; function B() {}; Object.setPrototypeOf(B, A); B instanceof A; // true!
(全文完)
參考資料Object.prototype.__proto__ - JavaScript | MDN
ECMAScript 2015 Language Specification
Babel · The compiler for writing next generation JavaScript
重編自我的博客,原文地址:https://idiotwu.me/proto-property-and-es6-classes-inheritance/
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/86037.html
摘要:面向對象里最大的特點應該就屬繼承了。在第二篇文章里說過原型實例跟構造函數之間的繼承,并且還講了一道推算題。 通過上一篇文章想必各位老鐵已經熟悉了class了,這篇文章接著介紹繼承。面向對象里最大的特點應該就屬繼承了。一個項目可能需要不斷的迭代、完善、升級。那每一次的更新你是要重新寫呢,還是在原有的基礎上改吧改吧呢?當然,不是缺心眼的人肯定都會在原來的基礎上改吧改吧,那這個改吧改吧就需要...
摘要:使用類創建實例對象也是直接對類使用命令,跟中構造函數的用法一致。中沒有構造函數,作為構造函數的語法糖,同時有屬性和屬性,因此同時存在兩條繼承鏈。子類的屬性,表示構造函數的繼承,總是指向父類。 1 Class in ES6 ES6提出了類(Class)的概念,讓對象的原型的寫法更像面向對象語言寫法。 ES6中通過class定義對象,默認具有constructor方法和自定義方法,但是包含...
摘要:使用類創建實例對象也是直接對類使用命令,跟中構造函數的用法一致。中沒有構造函數,作為構造函數的語法糖,同時有屬性和屬性,因此同時存在兩條繼承鏈。子類的屬性,表示構造函數的繼承,總是指向父類。 1 Class in ES6 ES6提出了類(Class)的概念,讓對象的原型的寫法更像面向對象語言寫法。 ES6中通過class定義對象,默認具有constructor方法和自定義方法,但是包含...
摘要:請看對應版本干了什么可知,相當于以前在構造函數里的行為。這種寫法會與上文中寫法有何區別我們在環境下運行一下,看看這兩種構造函數的有何區別打印結果打印結果結合上文中關于原型的論述,仔細品味這兩者的差別,最好手動嘗試一下。 ES6 class 在ES6版本之前,JavaScript語言并沒有傳統面向對象語言的class寫法,ES6發布之后,Babel迅速跟進,廣大開發者也很快喜歡上ES6帶...
閱讀 1650·2021-11-16 11:44
閱讀 2393·2021-10-11 11:07
閱讀 4036·2021-10-09 09:41
閱讀 663·2021-09-22 15:52
閱讀 3187·2021-09-09 09:33
閱讀 2702·2019-08-30 15:55
閱讀 2284·2019-08-30 15:55
閱讀 837·2019-08-30 15:55