摘要:即是由此我們可以輕松偽造一個(gè)實(shí)例對(duì)象可是這是對(duì)對(duì)象的屬性的修改,和有什么關(guān)系靜態(tài)方法的繼承少年,可別忘了函數(shù)本身也是個(gè)對(duì)象喲在上面的代碼中,我們使無關(guān)對(duì)象的指向構(gòu)造函數(shù)的,于是使被判定為的實(shí)例。
關(guān)于 __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__ 屬性指向了實(shí)例對(duì)象的原型 Constructor.prototype。那么,這個(gè)屬性里隱藏著怎樣的黑魔法呢?
ES6 class 的實(shí)現(xiàn)最近看 ECMAScript 6 的 spec,發(fā)現(xiàn)了一些有意思的東西,比如 class 章節(jié):
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).
...
這幾行規(guī)定了類繼承(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 變量的去向,發(fā)現(xiàn)是這樣的:
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] 對(duì)繼承的實(shí)現(xiàn)如下:
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]] 與原型鏈要檢測一個(gè)對(duì)象是否是一個(gè)構(gòu)造函數(shù)的實(shí)例,我們通常會(huì)用 O instanceof C 這樣的表達(dá)式,在 spec 中,instanceof 運(yùn)算符這樣被定義:
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 運(yùn)算符掉用了 Function.prototype 上的內(nèi)部方法 @@hasInstance,此方法將 this 對(duì)象(即 C)的 prototype 屬性與實(shí)例對(duì)象 O 的 [[prototype]] 對(duì)比,如果后者 [[prototype]] 為 null 則返回 false,如果兩者相等,則返回 true,否則沿原型鏈向上比較,直到得出結(jié)果。
即是:
O instanceof C => O.__proto__ === C.prototype ? true: O.__proto__.__proto__ === C.prototype ? true : ...
由此我們可以輕松偽造一個(gè)實(shí)例對(duì)象:
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
可是這是對(duì)對(duì)象的 __proto__ 屬性的修改,和 SubClass.__proto__ 有什么關(guān)系?
靜態(tài)方法的繼承少年,可別忘了 JavaScript 函數(shù)本身也是個(gè)對(duì)象喲!
在上面的代碼中,我們使無關(guān)對(duì)象 b 的 __proto__ 指向構(gòu)造函數(shù) A 的 prototype,于是使 b 被判定為 A 的實(shí)例。同時(shí),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!"
此時(shí),我們?cè)賹?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
如此一來,子類就構(gòu)造完成了!可以開開心心造孩子去了!
惡搞:讓函數(shù) B 成為函數(shù) A 的實(shí)例利用 instanceof 運(yùn)算符的定義,我們還能玩出一些神奇的事,比如:
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/
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/86037.html
摘要:面向?qū)ο罄镒畲蟮奶攸c(diǎn)應(yīng)該就屬繼承了。在第二篇文章里說過原型實(shí)例跟構(gòu)造函數(shù)之間的繼承,并且還講了一道推算題。 通過上一篇文章想必各位老鐵已經(jīng)熟悉了class了,這篇文章接著介紹繼承。面向?qū)ο罄镒畲蟮奶攸c(diǎn)應(yīng)該就屬繼承了。一個(gè)項(xiàng)目可能需要不斷的迭代、完善、升級(jí)。那每一次的更新你是要重新寫呢,還是在原有的基礎(chǔ)上改吧改吧呢?當(dāng)然,不是缺心眼的人肯定都會(huì)在原來的基礎(chǔ)上改吧改吧,那這個(gè)改吧改吧就需要...
摘要:使用類創(chuàng)建實(shí)例對(duì)象也是直接對(duì)類使用命令,跟中構(gòu)造函數(shù)的用法一致。中沒有構(gòu)造函數(shù),作為構(gòu)造函數(shù)的語法糖,同時(shí)有屬性和屬性,因此同時(shí)存在兩條繼承鏈。子類的屬性,表示構(gòu)造函數(shù)的繼承,總是指向父類。 1 Class in ES6 ES6提出了類(Class)的概念,讓對(duì)象的原型的寫法更像面向?qū)ο笳Z言寫法。 ES6中通過class定義對(duì)象,默認(rèn)具有constructor方法和自定義方法,但是包含...
摘要:使用類創(chuàng)建實(shí)例對(duì)象也是直接對(duì)類使用命令,跟中構(gòu)造函數(shù)的用法一致。中沒有構(gòu)造函數(shù),作為構(gòu)造函數(shù)的語法糖,同時(shí)有屬性和屬性,因此同時(shí)存在兩條繼承鏈。子類的屬性,表示構(gòu)造函數(shù)的繼承,總是指向父類。 1 Class in ES6 ES6提出了類(Class)的概念,讓對(duì)象的原型的寫法更像面向?qū)ο笳Z言寫法。 ES6中通過class定義對(duì)象,默認(rèn)具有constructor方法和自定義方法,但是包含...
摘要:也就是說,的構(gòu)造函數(shù),對(duì)應(yīng)的類的構(gòu)造方法。上面代碼表明,類的數(shù)據(jù)類型就是函數(shù),類本身就指向構(gòu)造函數(shù)。使用的時(shí)候,也是直接對(duì)類使用命令,跟構(gòu)造函數(shù)的用法完全一致。 OOP 標(biāo)簽(空格分隔): 未分類 ES5 構(gòu)造函數(shù)(constructor),其實(shí)就是一個(gè)普通函數(shù),但是內(nèi)部使用了this變量,對(duì)構(gòu)造函數(shù)使用new運(yùn)算符,就能生成實(shí)例,并且this變量會(huì)綁定在實(shí)例對(duì)象上。 var cat...
摘要:請(qǐng)看對(duì)應(yīng)版本干了什么可知,相當(dāng)于以前在構(gòu)造函數(shù)里的行為。這種寫法會(huì)與上文中寫法有何區(qū)別我們?cè)诃h(huán)境下運(yùn)行一下,看看這兩種構(gòu)造函數(shù)的有何區(qū)別打印結(jié)果打印結(jié)果結(jié)合上文中關(guān)于原型的論述,仔細(xì)品味這兩者的差別,最好手動(dòng)嘗試一下。 ES6 class 在ES6版本之前,JavaScript語言并沒有傳統(tǒng)面向?qū)ο笳Z言的class寫法,ES6發(fā)布之后,Babel迅速跟進(jìn),廣大開發(fā)者也很快喜歡上ES6帶...
閱讀 1673·2021-11-16 11:44
閱讀 2407·2021-10-11 11:07
閱讀 4073·2021-10-09 09:41
閱讀 677·2021-09-22 15:52
閱讀 3199·2021-09-09 09:33
閱讀 2715·2019-08-30 15:55
閱讀 2295·2019-08-30 15:55
閱讀 846·2019-08-30 15:55