摘要:前言見解有限,如有描述不當(dāng)之處,請幫忙及時指出,如有錯誤,會及時修正。倘若用的是中文搜索。所以最終的實例對象仍然能進(jìn)行正常的原型鏈回溯,回溯到原本的所有原型方法這樣通過一個巧妙的欺騙技巧,就實現(xiàn)了完美的繼承。
前言
見解有限,如有描述不當(dāng)之處,請幫忙及時指出,如有錯誤,會及時修正。
20180201更新:
修改用詞描述,如組合寄生式改成寄生組合式,修改多處筆誤(感謝@Yao Ding的反饋)
----------長文+多圖預(yù)警,需要花費一定時間----------
故事是從一次實際需求中開始的。。。
某天,某人向我尋求了一次幫助,要協(xié)助寫一個日期工具類,要求:
此類繼承自Date,擁有Date的所有屬性和對象
此類可以自由拓展方法
形象點描述,就是要求可以這樣:
// 假設(shè)最終的類是 MyDate,有一個getTest拓展方法 let date = new MyDate(); // 調(diào)用Date的方法,輸出GMT絕對毫秒數(shù) console.log(date.getTime()); // 調(diào)用拓展的方法,隨便輸出什么,譬如helloworld! console.log(date.getTest());
于是,隨手用JS中經(jīng)典的寄生組合式寫了一個繼承,然后,剛準(zhǔn)備完美收工,一運行,卻出現(xiàn)了以下的情景:
但是的心情是這樣的: ?囧
以前也沒有遇到過類似的問題,然后自己嘗試著用其它方法,多次嘗試,均無果(不算暴力混合法的情況),其實回過頭來看,是因為思路新奇,憑空想不到,并不是原理上有多難。。。
于是,借助強(qiáng)大的搜素引擎,搜集資料,最后,再自己總結(jié)了一番,才有了本文。
----------正文開始前----------
正文開始前,各位看官可以先暫停往下讀,嘗試下,在不借助任何網(wǎng)絡(luò)資料的情況下,是否能實現(xiàn)上面的需求?(就以10分鐘為限吧)
大綱
先說說如何快速快速尋求解答
stackoverflow上早就有答案了!
倘若用的是中文搜索。
分析問題的關(guān)鍵
經(jīng)典的繼承法有何問題
為什么無法被繼承?
該如何實現(xiàn)繼承?
暴力混合法
ES5黑魔法
ES6大法
ES6寫法,然后babel打包
幾種繼承的細(xì)微區(qū)別
ES6繼承與ES5繼承的區(qū)別
構(gòu)造函數(shù)與實例對象
[[Class]]與Internal slot
如何快速判斷是否繼承?
寫在最后的話
先說說如何快速快速尋求解答遇到不會的問題,肯定第一目標(biāo)就是如何快速尋求解決方案,答案是:
先去stackoverflow上看看有沒有類似的題。。。
于是,借助搜索引擎搜索了下,第一條就符合條件,點開進(jìn)去看描述
stackoverflow上早就有答案了!先說說結(jié)果,再瀏覽一番后,確實找到了解決方案,然后回過頭來一看,驚到了,因為這個問題的提問時間是6 years, 7 months ago。
也就是說,2011年的時候就已經(jīng)有人提出了。。。
感覺自己落后了一個時代>_<。。。
而且還發(fā)現(xiàn)了一個細(xì)節(jié),那就是viewed:10,606 times,也就是說至今一共也才一萬多次閱讀而已,考慮到前端行業(yè)的從業(yè)人數(shù),這個比例驚人的低。
以點見面,看來,遇到這個問題的人并不是很多。
用中文搜索并不丟人(我遇到問題時的本能反應(yīng)也是去百度)。結(jié)果是這樣的:
嗯,看來英文關(guān)鍵字搜索效果不錯,第一條就是符合要求的。然后又試了試中文搜索。
效果不如人意,搜索前幾頁,唯一有一條看起來比較相近的(segmentfault上的那條),點進(jìn)去看
怎么說呢。。。這個問題關(guān)注度不高,瀏覽器數(shù)較少,而且上面的問題描述和預(yù)期的有點區(qū)別,仍然是有人回答的。
不過,雖然說問題在一定程度上得到了解決,但是回答者繞過了無法繼承這個問題,有點未竟全功的意思。。。
借助stackoverflow上的回答
經(jīng)典的繼承法有何問題先看看本文最開始時提到的經(jīng)典繼承法實現(xiàn),如下:
/** * 經(jīng)典的js寄生組合式繼承 */ function MyDate() { Date.apply(this, arguments); this.abc = 1; } function inherits(subClass, superClass) { function Inner() {} Inner.prototype = superClass.prototype; subClass.prototype = new Inner(); subClass.prototype.constructor = subClass; } inherits(MyDate, Date); MyDate.prototype.getTest = function() { return this.getTime(); }; let date = new MyDate(); console.log(date.getTest());
就是這段代碼?,這也是JavaScript高程(紅寶書)中推薦的一種,一直用,從未失手,結(jié)果現(xiàn)在馬失前蹄。。。
我們再回顧下它的報錯:
再打印它的原型看看:
怎么看都沒問題,因為按照原型鏈回溯規(guī)則,Date的所有原型方法都可以通過MyDate對象的原型鏈往上回溯到。
再仔細(xì)看看,發(fā)現(xiàn)它的關(guān)鍵并不是找不到方法,而是this is not a Date object.
嗯哼,也就是說,關(guān)鍵是:由于調(diào)用的對象不是Date的實例,所以不允許調(diào)用,就算是自己通過原型繼承的也不行
為什么無法被繼承?首先,看看MDN上的解釋,上面有提到,JavaScript的日期對象只能通過JavaScript Date作為構(gòu)造函數(shù)來實例化。
然后再看看stackoverflow上的回答:
有提到,v8引擎底層代碼中有限制,如果調(diào)用對象的[[Class]]不是Date,則拋出錯誤。
總的來說,結(jié)合這兩點,可以得出一個結(jié)論:
要調(diào)用Date上方法的實例對象必須通過Date構(gòu)造出來,否則不允許調(diào)用Date的方法
該如何實現(xiàn)繼承?雖然原因找到了,但是問題仍然要解決啊,真的就沒辦法了么?當(dāng)然不是,事實上還是有不少實現(xiàn)的方法的。
暴力混合法首先,說說說下暴力的混合法,它是下面這樣子的:
說到底就是:內(nèi)部生成一個Date對象,然后此類暴露的方法中,把原有Date中所有的方法都代理一遍,而且嚴(yán)格來說,這根本算不上繼承(都沒有原型鏈回溯)。
ES5黑魔法然后,再看看ES5中如何實現(xiàn)?
// 需要考慮polyfill情況 Object.setPrototypeOf = Object.setPrototypeOf || function(obj, proto) { obj.__proto__ = proto; return obj; }; /** * 用了點技巧的繼承,實際上返回的是Date對象 */ function MyDate() { // bind屬于Function.prototype,接收的參數(shù)是:object, param1, params2... var dateInst = new(Function.prototype.bind.apply(Date, [Date].concat(Array.prototype.slice.call(arguments))))(); // 更改原型指向,否則無法調(diào)用MyDate原型上的方法 // ES6方案中,這里就是[[prototype]]這個隱式原型對象,在沒有標(biāo)準(zhǔn)以前就是__proto__ Object.setPrototypeOf(dateInst, MyDate.prototype); dateInst.abc = 1; return dateInst; } // 原型重新指回Date,否則根本無法算是繼承 Object.setPrototypeOf(MyDate.prototype, Date.prototype); MyDate.prototype.getTest = function getTest() { return this.getTime(); }; let date = new MyDate(); // 正常輸出,譬如1515638988725 console.log(date.getTest());
一眼看上去不知所措?沒關(guān)系,先看下圖來理解:(原型鏈關(guān)系一目了然)
可以看到,用的是非常巧妙的一種做法:
正常繼承的情況如下:
new MyDate()返回實例對象date是由MyDate構(gòu)造的
原型鏈回溯是: date(MyDate對象)->date.__proto__->MyDate.prototype->MyDate.prototype.__proto__->Date.prototype
這種做法的繼承的情況如下:
new MyDate()返回實例對象date是由Date構(gòu)造的
原型鏈回溯是: date(Date對象)->date.__proto__->MyDate.prototype->MyDate.prototype.__proto__->Date.prototype
可以看出,關(guān)鍵點在于:
構(gòu)造函數(shù)里返回了一個真正的Date對象(由Date構(gòu)造,所以有這些內(nèi)部類中的關(guān)鍵[[Class]]標(biāo)志),所以它有調(diào)用Date原型上方法的權(quán)利
構(gòu)造函數(shù)里的Date對象的[[ptototype]](對外,瀏覽器中可通過__proto__訪問)指向MyDate.prototype,然后MyDate.prototype再指向Date.prototype。
所以最終的實例對象仍然能進(jìn)行正常的原型鏈回溯,回溯到原本Date的所有原型方法
這樣通過一個巧妙的欺騙技巧,就實現(xiàn)了完美的Date繼承。不過補充一點,MDN上有提到盡量不要修改對象的[[Prototype]],因為這樣可能會干涉到瀏覽器本身的優(yōu)化。
如果你關(guān)心性能,你就不應(yīng)該在一個對象中修改它的 [[Prototype]]
ES6大法當(dāng)然,除了上述的ES5實現(xiàn),ES6中也可以直接繼承(自帶支持繼承Date),而且更為簡單:
class MyDate extends Date { constructor() { super(); this.abc = 1; } getTest() { return this.getTime(); } } let date = new MyDate(); // 正常輸出,譬如1515638988725 console.log(date.getTest());
對比下ES5中的實現(xiàn),這個真的是簡單的不行,直接使用ES6的Class語法就行了。
而且,也可以正常輸出。
注意:這里的正常輸出環(huán)境是直接用ES6運行,不經(jīng)過babel打包,打包后實質(zhì)上是轉(zhuǎn)化成ES5的,所以效果完全不一樣
ES6寫法,然后Babel打包雖然說上述ES6大法是可以直接繼承Date的,但是,考慮到實質(zhì)上大部分的生產(chǎn)環(huán)境是:ES6 + Babel
直接這樣用ES6 + Babel是會出問題的
不信的話,可以自行嘗試下,Babel打包成ES5后代碼大致是這樣的:
然后當(dāng)信心滿滿的開始用時,會發(fā)現(xiàn):
對,又出現(xiàn)了這個問題,也許這時候是這樣的⊙?⊙
因為轉(zhuǎn)譯后的ES5源碼中,仍然是通過MyDate來構(gòu)造,
而MyDate的構(gòu)造中又無法修改屬于Date內(nèi)部的[[Class]]之類的私有標(biāo)志,
因此構(gòu)造出的對象仍然不允許調(diào)用Date方法(調(diào)用時,被引擎底層代碼識別為[[Class]]標(biāo)志不符合,不允許調(diào)用,拋出錯誤)
由此可見,ES6繼承的內(nèi)部實現(xiàn)和Babel打包編譯出來的實現(xiàn)是有區(qū)別的。
(雖說Babel的polyfill一般會按照定義的規(guī)范去實現(xiàn)的,但也不要過度迷信)。
雖然上述提到的三種方法都可以達(dá)到繼承Date的目的-混合法嚴(yán)格說不能算繼承,只不過是另類實現(xiàn)。
于是,將所有能打印的主要信息都打印出來,分析幾種繼承的區(qū)別,大致場景是這樣的:
可以參考:( 請進(jìn)入調(diào)試模式)https://dailc.github.io/fe-interview/demo/extends_date.html
從上往下,1, 2, 3, 4四種繼承實現(xiàn)分別是:(排出了混合法)
ES6的Class大法
經(jīng)典寄生組合式繼承法
本文中的取巧做法,Date構(gòu)造實例,然后更改__proto__的那種
ES6的Class大法,Babel打包后的實現(xiàn)(無法正常調(diào)用的)
~~~~以下是MyDate們的prototype~~~~~~~~~ Date {constructor: ?, getTest: ?} Date {constructor: ?, getTest: ?} Date {getTest: ?, constructor: ?} Date {constructor: ?, getTest: ?} ~~~~以下是new出的對象~~~~~~~~~ Sat Jan 13 2018 21:58:55 GMT+0800 (CST) MyDate2 {abc: 1} Sat Jan 13 2018 21:58:55 GMT+0800 (CST) MyDate {abc: 1} ~~~~以下是new出的對象的Object.prototype.toString.call~~~~~~~~~ [object Date] [object Object] [object Date] [object Object] ~~~~以下是MyDate們的__proto__~~~~~~~~~ ? Date() { [native code] } ? () { [native code] } ? () { [native code] } ? Date() { [native code] } ~~~~以下是new出的對象的__proto__~~~~~~~~~ Date {constructor: ?, getTest: ?} Date {constructor: ?, getTest: ?} Date {getTest: ?, constructor: ?} Date {constructor: ?, getTest: ?} ~~~~以下是對象的__proto__與MyDate們的prototype比較~~~~~~~~~ true true true true
看出,主要差別有幾點:
MyDate們的__proto__指向不一樣
Object.prototype.toString.call的輸出不一樣
對象本質(zhì)不一樣,可以正常調(diào)用的1, 3都是Date構(gòu)造出的,而其它的則是MyDate構(gòu)造出的
我們上文中得出的一個結(jié)論是:由于調(diào)用的對象不是由Date構(gòu)造出的實例,所以不允許調(diào)用,就算是自己的原型鏈上有Date.prototype也不行
但是這里有兩個變量:分別是底層構(gòu)造實例的方法不一樣,以及對象的Object.prototype.toString.call的輸出不一樣。
(另一個MyDate.__proto__可以排除,因為原型鏈回溯肯定與它無關(guān))
萬一它的判斷是根據(jù)Object.prototype.toString.call來的呢?那這樣結(jié)論不就有誤差了?
于是,根據(jù)ES6中的,Symbol.toStringTag,使用黑魔法,動態(tài)的修改下它,排除下干擾:
// 分別可以給date2,date3設(shè)置 Object.defineProperty(date2, Symbol.toStringTag, { get: function() { return "Date"; } });
然后在打印下看看,變成這樣了:
[object Date] [object Date] [object Date] [object Object]
可以看到,第二個的MyDate2構(gòu)造出的實例,雖然打印出來是[object Date],但是調(diào)用Date方法仍然是有錯誤
此時我們可以更加準(zhǔn)確一點的確認(rèn):由于調(diào)用的對象不是由Date構(gòu)造出的實例,所以不允許調(diào)用
而且我們可以看到,就算通過黑魔法修改Object.prototype.toString.call,內(nèi)部的[[Class]]標(biāo)識位也是無法修改的。
(這塊知識點大概是Object.prototype.toString.call可以輸出內(nèi)部的[[Class]],但無法改變它,由于不是重點,這里不贅述)。
從上文中的分析可以看到一點:ES6的Class寫法繼承是沒問題的。但是換成ES5寫法就不行了。
所以ES6的繼承大法和ES5肯定是有區(qū)別的,那么究竟是哪里不同呢?(主要是結(jié)合的本文繼承Date來說)
區(qū)別:(以SubClass,SuperClass,instance為例)
ES5中繼承的實質(zhì)是:(那種經(jīng)典寄生組合式繼承法)
先由子類(SubClass)構(gòu)造出實例對象this
然后在子類的構(gòu)造函數(shù)中,將父類(SuperClass)的屬性添加到this上,SuperClass.apply(this, arguments)
子類原型(SubClass.prototype)指向父類原型(SuperClass.prototype)
所以instance是子類(SubClass)構(gòu)造出的(所以沒有父類的[[Class]]關(guān)鍵標(biāo)志)
所以,instance有SubClass和SuperClass的所有實例屬性,以及可以通過原型鏈回溯,獲取SubClass和SuperClass原型上的方法
ES6中繼承的實質(zhì)是:
先由父類(SuperClass)構(gòu)造出實例對象this,這也是為什么必須先調(diào)用父類的super()方法(子類沒有自己的this對象,需先由父類構(gòu)造)
然后在子類的構(gòu)造函數(shù)中,修改this(進(jìn)行加工),譬如讓它指向子類原型(SubClass.prototype),這一步很關(guān)鍵,否則無法找到子類原型(注,子類構(gòu)造中加工這一步的實際做法是推測出的,從最終效果來推測)
然后同樣,子類原型(SubClass.prototype)指向父類原型(SuperClass.prototype)
所以instance是父類(SuperClass)構(gòu)造出的(所以有著父類的[[Class]]關(guān)鍵標(biāo)志)
所以,instance有SubClass和SuperClass的所有實例屬性,以及可以通過原型鏈回溯,獲取SubClass和SuperClass原型上的方法
以上?就列舉了些重要信息,其它的如靜態(tài)方法的繼承沒有贅述。(靜態(tài)方法繼承實質(zhì)上只需要更改下SubClass.__proto__到SuperClass即可)
可以看著這張圖快速理解:
有沒有發(fā)現(xiàn)呢:ES6中的步驟和本文中取巧繼承Date的方法一模一樣,不同的是ES6是語言底層的做法,有它的底層優(yōu)化之處,而本文中的直接修改__proto__容易影響性能
ES6中在super中構(gòu)建this的好處?
因為ES6中允許我們繼承內(nèi)置的類,如Date,Array,Error等。如果this先被創(chuàng)建出來,在傳給Array等系統(tǒng)內(nèi)置類的構(gòu)造函數(shù),這些內(nèi)置類的構(gòu)造函數(shù)是不認(rèn)這個this的。
所以需要現(xiàn)在super中構(gòu)建出來,這樣才能有著super中關(guān)鍵的[[Class]]標(biāo)志,才能被允許調(diào)用。(否則就算繼承了,也無法調(diào)用這些內(nèi)置類的方法)
看到這里,不知道是否對上文中頻繁提到的構(gòu)造函數(shù),實例對象有所混淆與困惑呢?這里稍微描述下:
要弄懂這一點,需要先知道new一個對象到底發(fā)生了什么?先形象點說:
new MyClass()中,都做了些什么工作function MyClass() { this.abc = 1; } MyClass.prototype.print = function() { console.log("this.abc:" + this.abc); }; let instance = new MyClass();
譬如,上述就是一個標(biāo)準(zhǔn)的實例對象生成,都發(fā)生了什么呢?
步驟簡述如下:(參考MDN,還有部分關(guān)于底層的描述略去-如[[Class]]標(biāo)識位等)
構(gòu)造函數(shù)內(nèi)部,創(chuàng)建一個新的對象,它繼承自MyClass.prototype,let instance = Object.create(MyClass.prototype);
使用指定的參數(shù)調(diào)用構(gòu)造函數(shù)MyClass,并將 this綁定到新創(chuàng)建的對象,MyClass.call(instance);,執(zhí)行后擁有所有實例屬性
如果構(gòu)造函數(shù)返回了一個“對象”,那么這個對象會取代整個new出來的結(jié)果。如果構(gòu)造函數(shù)沒有返回對象,那么new出來的結(jié)果為步驟1創(chuàng)建的對象。
(一般情況下構(gòu)造函數(shù)不返回任何值,不過用戶如果想覆蓋這個返回值,可以自己選擇返回一個普通對象來覆蓋。當(dāng)然,返回數(shù)組也會覆蓋,因為數(shù)組也是對象。)
結(jié)合上述的描述,大概可以還原成以下代碼:(簡單還原,不考慮各種其它邏輯)
let instance = Object.create(MyClass.prototype); let innerConstructReturn = MyClass.call(instance); let innerConstructReturnIsObj = typeof innerConstructReturn === "object" || typeof innerConstructReturn === "function"; return innerConstructReturnIsObj ? innerConstructReturn : instance;
注意??:
普通的函數(shù)構(gòu)建,可以簡單的認(rèn)為就是上述步驟
實際上對于一些內(nèi)置類(如Date等),并沒有這么簡單,還有一些自己的隱藏邏輯,譬如[[Class]]標(biāo)識位等一些重要私有屬性。
譬如可以在MDN上看到,以常規(guī)函數(shù)調(diào)用Date(即不加 new 操作符)將會返回一個字符串,而不是一個日期對象,如果這樣模擬的話會無效
覺得看起來比較繁瑣?可以看下圖梳理:
那現(xiàn)在再回頭看看。
什么是構(gòu)造函數(shù)?
如上述中的MyClass就是一個構(gòu)造函數(shù),在內(nèi)部它構(gòu)造出了instance對象
什么是實例對象?
instance就是一個實例對象,它是通過new出來的?
實例與構(gòu)造的關(guān)系
有時候淺顯點,可以認(rèn)為構(gòu)造函數(shù)是xxx就是xxx的實例。即:
let instance = new MyClass();
此時我們就可以認(rèn)為instance是MyClass的實例,因為它的構(gòu)造函數(shù)就是它
實例就一定是由對應(yīng)的構(gòu)造函數(shù)構(gòu)造出的么?不一定,我們那ES5黑魔法來做示例
function MyDate() { // bind屬于Function.prototype,接收的參數(shù)是:object, param1, params2... var dateInst = new(Function.prototype.bind.apply(Date, [Date].concat(Array.prototype.slice.call(arguments))))(); // 更改原型指向,否則無法調(diào)用MyDate原型上的方法 // ES6方案中,這里就是[[prototype]]這個隱式原型對象,在沒有標(biāo)準(zhǔn)以前就是__proto__ Object.setPrototypeOf(dateInst, MyDate.prototype); dateInst.abc = 1; return dateInst; }
我們可以看到instance的最終指向的原型是MyDate.prototype,而MyDate.prototype的構(gòu)造函數(shù)是MyDate,
因此可以認(rèn)為instance是MyDate的實例。
但是,實際上,instance卻是由Date構(gòu)造的
我們可以繼續(xù)用ES6中的new.target來驗證。
注意??
關(guān)于new.target,MDN中的定義是:new.target返回一個指向構(gòu)造方法或函數(shù)的引用。
嗯哼,也就是說,返回的是構(gòu)造函數(shù)。
我們可以在相應(yīng)的構(gòu)造中測試打印:
class MyDate extends Date { constructor() { super(); this.abc = 1; console.log("~~~new.target.name:MyDate~~~~"); console.log(new.target.name); } } // new操作時的打印結(jié)果是: // ~~~new.target.name:MyDate~~~~ // MyDate
然后,可以在上面的示例中看到,就算是ES6的Class繼承,MyDate構(gòu)造中打印new.target也顯示MyDate,
但實際上它是由Date來構(gòu)造(有著Date關(guān)鍵的[[Class]]標(biāo)志,因為如果不是Date構(gòu)造(如沒有標(biāo)志)是無法調(diào)用Date的方法的)。
所以,實際上用new.target是無法判斷實例對象到底是由哪一個構(gòu)造構(gòu)造的(這里指的是判斷底層真正的[[Class]]標(biāo)志來源的構(gòu)造)
在MDN上的定義也可以看到,new.target返回的是直接構(gòu)造函數(shù)(new作用的那個),所以請不要將直接構(gòu)造函數(shù)與實際上的構(gòu)造搞混
再回到結(jié)論:實例對象不一定就是由它的原型上的構(gòu)造函數(shù)構(gòu)造的,有可能構(gòu)造函數(shù)內(nèi)部有著寄生等邏輯,偷偷的用另一個函數(shù)來構(gòu)造了下,
當(dāng)然,簡單情況下,我們直接說實例對象由對應(yīng)構(gòu)造函數(shù)構(gòu)造也沒錯(不過,在涉及到這種Date之類的分析時,我們還是得明白)。
這一部分為補充內(nèi)容。
前文中一直提到一個概念:Date內(nèi)部的[[Class]]標(biāo)識
其實,嚴(yán)格來說,不能這樣泛而稱之(前文中只是用這個概念是為了降低復(fù)雜度,便于理解),它可以分為以下兩部分:
在ES5中,每種內(nèi)置對象都定義了 [[Class]] 內(nèi)部屬性的值,[[Class]] 內(nèi)部屬性的值用于內(nèi)部區(qū)分對象的種類
Object.prototype.toString訪問的就是這個[[Class]]
規(guī)范中除了通過Object.prototype.toString,沒有提供任何手段使程序訪問此值。
而且Object.prototype.toString輸出無法被修改
而在ES6中,之前的 [[Class]] 不再使用,取而代之的是一系列的internal slot
Internal slot 對應(yīng)于與對象相關(guān)聯(lián)并由各種ECMAScript規(guī)范算法使用的內(nèi)部狀態(tài),它們沒有對象屬性,也不能被繼承
根據(jù)具體的 Internal slot 規(guī)范,這種狀態(tài)可以由任何ECMAScript語言類型或特定ECMAScript規(guī)范類型值的值組成
通過Object.prototype.toString,仍然可以輸出Internal slot值
簡單點理解(簡化理解),Object.prototype.toString的流程是:如果是基本數(shù)據(jù)類型(除去Object以外的幾大類型),則返回原本的slot,
如果是Object類型(包括內(nèi)置對象以及自己寫的對象),則調(diào)用`Symbol.toStringTag` - `Symbol.toStringTag`方法的默認(rèn)實現(xiàn)就是返回對象的Internal slot,這個方法**可以被重寫**
這兩點是有所差異的,需要區(qū)分(不過簡單點可以統(tǒng)一理解為內(nèi)置對象內(nèi)部都有一個特殊標(biāo)識,用來區(qū)分對應(yīng)類型-不符合類型就不給調(diào)用)。
JS內(nèi)置對象是這些:
"Arguments", "Array", "Boolean", "Date", "Error", "Function", "JSON", "Math", "Number", "Object", "RegExp", "String"
ES6新增的一些,這里未提到:(如Promise對象可以輸出[object Promise])
而前文中提到的:
Object.defineProperty(date, Symbol.toStringTag, { get: function() { return "Date"; } });
它的作用是重寫Symbol.toStringTag,截取date(雖然是內(nèi)置對象,但是仍然屬于Object)的Object.prototype.toString的輸出,讓這個對象輸出自己修改后的[object Date]。
但是,僅僅是做到輸出的時候變成了Date,實際上內(nèi)部的internal slot值并沒有被改變,因此仍然不被認(rèn)為是Date
如何快速判斷是否繼承?其實,在判斷繼承時,沒有那么多的技巧,就只有關(guān)鍵的一點:[[prototype]](__ptoto__)的指向關(guān)系
譬如:
console.log(instance instanceof SubClass); console.log(instance instanceof SuperClass);
實質(zhì)上就是:
SubClass.prototype是否出現(xiàn)在instance的原型鏈上
SuperClass.prototype是否出現(xiàn)在instance的原型鏈上
然后,對照本文中列舉的一些圖,一目了然就可以看清關(guān)系。有時候,完全沒有必要弄的太復(fù)雜。
寫在最后的話由于繼承的介紹在網(wǎng)上已經(jīng)多不勝數(shù),因此本文沒有再重復(fù)描述,而是由一道Date繼承題引發(fā),展開。(關(guān)鍵就是原型鏈)
不知道看到這里,各位看官是否都已經(jīng)弄懂了JS中的繼承呢?
另外,遇到問題時,多想一想,有時候你會發(fā)現(xiàn),其實你知道的并不是那么多,然后再想一想,又會發(fā)現(xiàn)其實并沒有這么復(fù)雜。。。
附錄 博客初次發(fā)布2018.01.15于我個人博客上面
http://www.dailichun.com/2018/01/15/howtoextenddate.html
參考資料https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes
https://stackoverflow.com/questions/6075231/how-to-extend-the-javascript-date-object/30882416
http://exploringjs.com/es6/ch_classes.html#sec_essentials-classes
http://blog.csdn.net/github_36978270/article/details/71896444
http://blog.csdn.net/pcaxb/article/details/53784309
http://blog.csdn.net/kittyjie/article/details/50494915
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/92503.html
摘要:函數(shù)式編程前端掘金引言面向?qū)ο缶幊桃恢币詠矶际侵械闹鲗?dǎo)范式。函數(shù)式編程是一種強(qiáng)調(diào)減少對程序外部狀態(tài)產(chǎn)生改變的方式。 JavaScript 函數(shù)式編程 - 前端 - 掘金引言 面向?qū)ο缶幊桃恢币詠矶际荍avaScript中的主導(dǎo)范式。JavaScript作為一門多范式編程語言,然而,近幾年,函數(shù)式編程越來越多得受到開發(fā)者的青睞。函數(shù)式編程是一種強(qiáng)調(diào)減少對程序外部狀態(tài)產(chǎn)生改變的方式。因此,...
摘要:之二關(guān)于原型開篇我記得初學(xué)時,最難懂的概念就是的原型,而且這個概念在筆試面試中常常提到,因此今天我們把這個概念拿出來,好好聊一聊。 之二:關(guān)于js原型 1. 開篇 我記得初學(xué)js時,最難懂的概念就是js的原型,而且這個概念在筆試面試中常常提到,因此今天我們把這個概念拿出來,好好聊一聊。 在仔細(xì)講解之前,我們先來看一道題,這道題來自JavaScript高級程序設(shè)計中原型鏈那一節(jié): fun...
摘要:返回值這段在下方應(yīng)用中有詳細(xì)的示例解析?;卣{(diào)函數(shù)丟失的解決方案綁定回調(diào)函數(shù)的指向這是典型的應(yīng)用場景綁定指向,用做回調(diào)函數(shù)。 showImg(https://segmentfault.com/img/remote/1460000019971331?w=1024&h=680); 函數(shù)原型鏈中的 apply,call 和 bind 方法是 JavaScript 中相當(dāng)重要的概念,與 this...
摘要:理解的函數(shù)基礎(chǔ)要搞好深入淺出原型使用原型模型,雖然這經(jīng)常被當(dāng)作缺點提及,但是只要善于運用,其實基于原型的繼承模型比傳統(tǒng)的類繼承還要強(qiáng)大。中文指南基本操作指南二繼續(xù)熟悉的幾對方法,包括,,。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處。 怎樣使用 this 因為本人屬于偽前端,因此文中只看懂了 8 成左右,希望能夠給大家?guī)韼椭?...(據(jù)說是阿里的前端妹子寫的) this 的值到底...
摘要:但這兩個對象的原型指向了同一個實例對象,這個實例對象同樣是存在棧內(nèi)存中的然后指向了一個對象。實際是在的實例對象增加一個屬性,并將屬性賦值為,但它并沒有修改原型鏈上的屬性。側(cè)重理解的指向問題 直接先貼題目吧 function A() { this.name = a this.color = [green, yellow] } function B() { ...
閱讀 3500·2019-08-30 15:53
閱讀 3414·2019-08-29 16:54
閱讀 2202·2019-08-29 16:41
閱讀 2412·2019-08-23 16:10
閱讀 3384·2019-08-23 15:04
閱讀 1355·2019-08-23 13:58
閱讀 355·2019-08-23 11:40
閱讀 2459·2019-08-23 10:26