掃二維碼與項(xiàng)目經(jīng)理溝通
我們?cè)谖⑿派?4小時(shí)期待你的聲音
解答本文疑問(wèn)/技術(shù)咨詢/運(yùn)營(yíng)咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流
故事是從一次實(shí)際需求中開(kāi)始的。

創(chuàng)新互聯(lián)-云計(jì)算及IDC服務(wù)提供商,涵蓋公有云、IDC機(jī)房租用、成都服務(wù)器托管、等保安全、私有云建設(shè)等企業(yè)級(jí)互聯(lián)網(wǎng)基礎(chǔ)服務(wù),溝通電話:028-86922220
某天,某人向我尋求了一次幫助,要協(xié)助寫一個(gè)日期工具類,要求:
此類繼承自 Date,擁有Date的所有屬性和對(duì)象
此類可以自由拓展方法
形象點(diǎn)描述,就是要求可以這樣:
// 假設(shè)最終的類是 MyDate,有一個(gè)getTest拓展方法
let date = new MyDate();// 調(diào)用Date的方法,輸出GMT絕對(duì)毫秒數(shù)
console.log(date.getTime());
// 調(diào)用拓展的方法,隨便輸出什么,譬如helloworld!
console.log(date.getTest());
于是,隨手用JS中經(jīng)典的組合寄生法寫了一個(gè)繼承,然后,剛準(zhǔn)備***收工,一運(yùn)行,卻出現(xiàn)了以下的情景:
但是的心情是這樣的: 囧
以前也沒(méi)有遇到過(guò)類似的問(wèn)題,然后自己嘗試著用其它方法,多次嘗試,均無(wú)果(不算暴力混合法的情況),其實(shí)回過(guò)頭來(lái)看,是因?yàn)樗悸沸缕妫瑧{空想不到,并不是原理上有多難。
于是,借助強(qiáng)大的搜素引擎,搜集資料,***,再自己總結(jié)了一番,才有了本文。
正文開(kāi)始前,各位看官可以先暫停往下讀,嘗試下,在不借助任何網(wǎng)絡(luò)資料的情況下,是否能實(shí)現(xiàn)上面的需求?(就以 10分鐘為限吧)
借助stackoverflow上的回答。
先看看本文最開(kāi)始時(shí)提到的經(jīng)典繼承法實(shí)現(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)在馬失前蹄。
我們?cè)倩仡櫹滤膱?bào)錯(cuò):
再打印它的原型看看:
怎么看都沒(méi)問(wèn)題,因?yàn)榘凑赵玩溁厮菀?guī)則, Date的所有原型方法都可以通過(guò) MyDate對(duì)象的原型鏈往上回溯到。再仔細(xì)看看,發(fā)現(xiàn)它的關(guān)鍵并不是找不到方法,而是 thisisnotaDateobject.
嗯哼,也就是說(shuō),關(guān)鍵是:由于調(diào)用的對(duì)象不是Date的實(shí)例,所以不允許調(diào)用,就算是自己通過(guò)原型繼承的也不行。
首先,看看 MDN上的解釋,上面有提到,JavaScript的日期對(duì)象只能通過(guò) JavaScriptDate作為構(gòu)造函數(shù)來(lái)實(shí)例化。
然后再看看stackoverflow上的回答:
有提到, v8引擎底層代碼中有限制,如果調(diào)用對(duì)象的 [[Class]]不是 Date,則拋出錯(cuò)誤。
總的來(lái)說(shuō),結(jié)合這兩點(diǎn),可以得出一個(gè)結(jié)論:要調(diào)用Date上方法的實(shí)例對(duì)象必須通過(guò)Date構(gòu)造出來(lái),否則不允許調(diào)用Date的方法。
雖然原因找到了,但是問(wèn)題仍然要解決啊,真的就沒(méi)辦法了么?當(dāng)然不是,事實(shí)上還是有不少實(shí)現(xiàn)的方法的。
首先,說(shuō)說(shuō)說(shuō)下暴力的混合法,它是下面這樣子的:
說(shuō)到底就是:內(nèi)部生成一個(gè) Date對(duì)象,然后此類暴露的方法中,把原有 Date中所有的方法都代理一遍,而且嚴(yán)格來(lái)說(shuō),這根本算不上繼承(都沒(méi)有原型鏈回溯)。
然后,再看看ES5中如何實(shí)現(xiàn)?
// 需要考慮polyfill情況
Object.setPrototypeOf = Object.setPrototypeOf ||
function(obj, proto) {
obj.__proto__ = proto;return obj;
};/**
* 用了點(diǎn)技巧的繼承,實(shí)際上返回的是Date對(duì)象
*/
function MyDate() {
// bind屬于Function.prototype,接收的參數(shù)是:object, param1, params2...
var dateInst = new(Function.prototype.bind.apply(Date, [Date].concat(Array.prototype.slice.call(arguments))))();// 更改原型指向,否則無(wú)法調(diào)用MyDate原型上的方法
// ES6方案中,這里就是[[prototype]]這個(gè)隱式原型對(duì)象,在沒(méi)有標(biāo)準(zhǔn)以前就是__proto__
Object.setPrototypeOf(dateInst, MyDate.prototype);dateInst.abc =
1;return dateInst;
}// 原型重新指回Date,否則根本無(wú)法算是繼承
Object.setPrototypeOf(MyDate.prototype, Date.prototype);MyDate.prototype.getTest = function getTest() {
return this.getTime();
};let date = new MyDate();
// 正常輸出,譬如1515638988725
console.log(date.getTest());
一眼看上去不知所措?沒(méi)關(guān)系,先看下圖來(lái)理解:(原型鏈關(guān)系一目了然)
可以看到,用的是非常巧妙的一種做法:
正常繼承的情況如下:
newMyDate()返回實(shí)例對(duì)象 date是由 MyDate構(gòu)造的
原型鏈回溯是: date(MyDate對(duì)象)->date.__proto__->MyDate.prototype->MyDate.prototype.__proto__->Date.prototype
這種做法的繼承的情況如下:
newMyDate()返回實(shí)例對(duì)象 date是由 Date構(gòu)造的
原型鏈回溯是: date(Date對(duì)象)->date.__proto__->MyDate.prototype->MyDate.prototype.__proto__->Date.prototype
可以看出,關(guān)鍵點(diǎn)在于:
構(gòu)造函數(shù)里返回了一個(gè)真正的 Date對(duì)象(由 Date構(gòu)造,所以有這些內(nèi)部類中的關(guān)鍵 [[Class]]標(biāo)志),所以它有調(diào)用 Date原型上方法的權(quán)利
構(gòu)造函數(shù)里的Date對(duì)象的 [[ptototype]](對(duì)外,瀏覽器中可通過(guò) __proto__訪問(wèn))指向 MyDate.prototype,然后 MyDate.prototype再指向 Date.prototype。
所以最終的實(shí)例對(duì)象仍然能進(jìn)行正常的原型鏈回溯,回溯到原本Date的所有原型方法。
這樣通過(guò)一個(gè)巧妙的欺騙技巧,就實(shí)現(xiàn)了***的Date繼承。不過(guò)補(bǔ)充一點(diǎn), MDN上有提到盡量不要修改對(duì)象的 [[Prototype]],因?yàn)檫@樣可能會(huì)干涉到瀏覽器本身的優(yōu)化。如果你關(guān)心性能,你就不應(yīng)該在一個(gè)對(duì)象中修改它的 [[Prototype]]
當(dāng)然,除了上述的ES5實(shí)現(xiàn),ES6中也可以直接繼承(自帶支持繼承 Date),而且更為簡(jiǎn)單:
class MyDate extends Date {
constructor() {
super();
this.abc = 1;
}
getTest() {
return this.getTime();
}
}let date = new MyDate();
// 正常輸出,譬如1515638988725
console.log(date.getTest());
對(duì)比下ES5中的實(shí)現(xiàn),這個(gè)真的是簡(jiǎn)單的不行,直接使用ES6的Class語(yǔ)法就行了。而且,也可以正常輸出。
注意:這里的正常輸出環(huán)境是直接用ES6運(yùn)行,不經(jīng)過(guò)babel打包,打包后實(shí)質(zhì)上是轉(zhuǎn)化成ES5的,所以效果完全不一樣。
雖然說(shuō)上述ES6大法是可以直接繼承Date的,但是,考慮到實(shí)質(zhì)上大部分的生產(chǎn)環(huán)境是: ES6+Babel
直接這樣用ES6 + Babel是會(huì)出問(wèn)題的。
不信的話,可以自行嘗試下,Babel打包成ES5后代碼大致是這樣的:
然后當(dāng)信心滿滿的開(kāi)始用時(shí),會(huì)發(fā)現(xiàn):
對(duì),又出現(xiàn)了這個(gè)問(wèn)題,也許這時(shí)候是這樣的⊙?⊙
因?yàn)檗D(zhuǎn)譯后的ES5源碼中,仍然是通過(guò) MyDate來(lái)構(gòu)造,而 MyDate的構(gòu)造中又無(wú)法修改屬于 Date內(nèi)部的 [[Class]]之類的私有標(biāo)志,因此構(gòu)造出的對(duì)象仍然不允許調(diào)用 Date方法(調(diào)用時(shí),被引擎底層代碼識(shí)別為 [[Class]]標(biāo)志不符合,不允許調(diào)用,拋出錯(cuò)誤)。
由此可見(jiàn),ES6繼承的內(nèi)部實(shí)現(xiàn)和Babel打包編譯出來(lái)的實(shí)現(xiàn)是有區(qū)別的。(雖說(shuō)Babel的polyfill一般會(huì)按照定義的規(guī)范去實(shí)現(xiàn)的,但也不要過(guò)度迷信)。
雖然上述提到的三種方法都可以達(dá)到繼承 Date的目的-混合法嚴(yán)格說(shuō)不能算繼承,只不過(guò)是另類實(shí)現(xiàn)。
于是,將所有能打印的主要信息都打印出來(lái),分析幾種繼承的區(qū)別,大致場(chǎng)景是這樣的:
可以參考:( 請(qǐng)進(jìn)入調(diào)試模式)https://dailc.github.io/fe-interview/demo/extends_date.html
從上往下, 1,2,3,4四種繼承實(shí)現(xiàn)分別是:(排出了混合法)
ES6的Class大法
經(jīng)典組合寄生繼承法
本文中的取巧做法,Date構(gòu)造實(shí)例,然后更改 __proto__的那種
ES6的Class大法,Babel打包后的實(shí)現(xiàn)(無(wú)法正常調(diào)用的)
~~~~以下是MyDate們的prototype~~~~~~~~~
Date {constructor: ?, getTest: ?}
Date {constructor: ?, getTest: ?}
Date {getTest: ?, constructor: ?}
Date {constructor: ?, getTest: ?}
~~~~以下是new出的對(duì)象~~~~~~~~~
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出的對(duì)象的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出的對(duì)象的__proto__~~~~~~~~~
Date {constructor: ?, getTest: ?}
Date {constructor: ?, getTest: ?}
Date {getTest: ?, constructor: ?}
Date {constructor: ?, getTest: ?}
~~~~以下是對(duì)象的__proto__與MyDate們的prototype比較~~~~~~~~~
true
true
true
true
看出,主要差別有幾點(diǎn):
MyDate們的proto指向不一樣
Object.prototype.toString.call的輸出不一樣
對(duì)象本質(zhì)不一樣,可以正常調(diào)用的 1,3都是 Date構(gòu)造出的,而其它的則是 MyDate構(gòu)造出的
我們上文中得出的一個(gè)結(jié)論是:由于調(diào)用的對(duì)象不是由Date構(gòu)造出的實(shí)例,所以不允許調(diào)用,就算是自己的原型鏈上有Date.prototype也不行
但是這里有兩個(gè)變量:分別是底層構(gòu)造實(shí)例的方法不一樣,以及對(duì)象的 Object.prototype.toString.call的輸出不一樣(另一個(gè) MyDate.__proto__可以排除,因?yàn)樵玩溁厮菘隙ㄅc它無(wú)關(guān))。
萬(wàn)一它的判斷是根據(jù) Object.prototype.toString.call來(lái)的呢?那這樣結(jié)論不就有誤差了?
于是,根據(jù)ES6中的, Symbol.toStringTag,使用黑魔法,動(dòng)態(tài)的修改下它,排除下干擾:
// 分別可以給date2,date3設(shè)置
Object.defineProperty(date2, Symbol.toStringTag, {
get: function() {
return "Date";
}
});
然后在打印下看看,變成這樣了:
[object Date]
[object Date]
[object Date]
[object Object]
可以看到,第二個(gè)的 MyDate2構(gòu)造出的實(shí)例,雖然打印出來(lái)是 [objectDate],但是調(diào)用Date方法仍然是有錯(cuò)誤。
此時(shí)我們可以更加準(zhǔn)確一點(diǎn)的確認(rèn):由于調(diào)用的對(duì)象不是由Date構(gòu)造出的實(shí)例,所以不允許調(diào)用。
而且我們可以看到,就算通過(guò)黑魔法修改 Object.prototype.toString.call,內(nèi)部的 [[Class]]標(biāo)識(shí)位也是無(wú)法修改的。(這塊知識(shí)點(diǎn)大概是Object.prototype.toString.call可以輸出內(nèi)部的[[Class]],但無(wú)法改變它,由于不是重點(diǎn),這里不贅述)。
從上午中的分析可以看到一點(diǎn):ES6的Class寫法繼承是沒(méi)問(wèn)題的。但是換成ES5寫法就不行了。
所以ES6的繼承大法和ES5肯定是有區(qū)別的,那么究竟是哪里不同呢?(主要是結(jié)合的本文繼承Date來(lái)說(shuō))
區(qū)別:(以 SubClass, SuperClass, instance為例)
ES5中繼承的實(shí)質(zhì)是:(那種經(jīng)典組合寄生繼承法)
先由子類( SubClass)構(gòu)造出實(shí)例對(duì)象this
然后在子類的構(gòu)造函數(shù)中,將父類( SuperClass)的屬性添加到 this上, SuperClass.apply(this,arguments)
子類原型( SubClass.prototype)指向父類原型( SuperClass.prototype)
所以 instance是子類( SubClass)構(gòu)造出的(所以沒(méi)有父類的 [[Class]]關(guān)鍵標(biāo)志)
所以, instance有 SubClass和 SuperClass的所有實(shí)例屬性,以及可以通過(guò)原型鏈回溯,獲取 SubClass和 SuperClass原型上的方法
ES6中繼承的實(shí)質(zhì)是:
先由父類( SuperClass)構(gòu)造出實(shí)例對(duì)象this,這也是為什么必須先調(diào)用父類的 super()方法(子類沒(méi)有自己的this對(duì)象,需先由父類構(gòu)造)
然后在子類的構(gòu)造函數(shù)中,修改this(進(jìn)行加工),譬如讓它指向子類原型( SubClass.prototype),這一步很關(guān)鍵,否則無(wú)法找到子類原型(注,子類構(gòu)造中加工這一步的實(shí)際做法是推測(cè)出的,從最終效果來(lái)推測(cè))
然后同樣,子類原型( SubClass.prototype)指向父類原型( SuperClass.prototype)
所以 instance是父類( SuperClass)構(gòu)造出的(所以有著父類的 [[Class]]關(guān)鍵標(biāo)志)
所以, instance有 SubClass和 SuperClass的所有實(shí)例屬性,以及可以通過(guò)原型鏈回溯,獲取 SubClass和 SuperClass原型上的方法
以上?就列舉了些重要信息,其它的如靜態(tài)方法的繼承沒(méi)有贅述。(靜態(tài)方法繼承實(shí)質(zhì)上只需要更改下 SubClass.__proto__到 SuperClass即可)
可以看著這張圖快速理解:
有沒(méi)有發(fā)現(xiàn)呢:ES6中的步驟和本文中取巧繼承Date的方法一模一樣,不同的是ES6是語(yǔ)言底層的做法,有它的底層優(yōu)化之處,而本文中的直接修改_proto_容易影響性能。
ES6中在super中構(gòu)建this的好處?
因?yàn)镋S6中允許我們繼承內(nèi)置的類,如Date,Array,Error等。如果this先被創(chuàng)建出來(lái),在傳給Array等系統(tǒng)內(nèi)置類的構(gòu)造函數(shù),這些內(nèi)置類的構(gòu)造函數(shù)是不認(rèn)這個(gè)this的。所以需要現(xiàn)在super中構(gòu)建出來(lái),這樣才能有著super中關(guān)鍵的 [[Class]]標(biāo)志,才能被允許調(diào)用。(否則就算繼承了,也無(wú)法調(diào)用這些內(nèi)置類的方法)
看到這里,不知道是否對(duì)上午中頻繁提到的構(gòu)造函數(shù),實(shí)例對(duì)象有所混淆與困惑呢?這里稍微描述下。
要弄懂這一點(diǎn),需要先知道 new一個(gè)對(duì)象到底發(fā)生了什么?先形象點(diǎn)說(shuō):
function MyClass() {
this.abc = 1;
}
MyClass.prototype.print = function() {
console.log('this.abc:' + this.abc);
};
let instance = new MyClass();
譬如,上述就是一個(gè)標(biāo)準(zhǔn)的實(shí)例對(duì)象生成,都發(fā)生了什么呢?
步驟簡(jiǎn)述如下:(參考MDN,還有部分關(guān)于底層的描述略去-如[[Class]]標(biāo)識(shí)位等)
構(gòu)造函數(shù)內(nèi)部,創(chuàng)建一個(gè)新的對(duì)象,它繼承自 MyClass.prototype, letinstance=Object.create(MyClass.prototype);
使用指定的參數(shù)調(diào)用構(gòu)造函數(shù) MyClass,并將 this綁定到新創(chuàng)建的對(duì)象, MyClass.call(instance);,執(zhí)行后擁有所有實(shí)例屬性
如果構(gòu)造函數(shù)返回了一個(gè)“對(duì)象”,那么這個(gè)對(duì)象會(huì)取代整個(gè) new出來(lái)的結(jié)果。如果構(gòu)造函數(shù)沒(méi)有返回對(duì)象,那么new出來(lái)的結(jié)果為步驟1創(chuàng)建的對(duì)象。 (一般情況下構(gòu)造函數(shù)不返回任何值,不過(guò)用戶如果想覆蓋這個(gè)返回值,可以自己選擇返回一個(gè)普通對(duì)象來(lái)覆蓋。當(dāng)然,返回?cái)?shù)組也會(huì)覆蓋,因?yàn)閿?shù)組也是對(duì)象。)
結(jié)合上述的描述,大概可以還原成以下代碼(簡(jiǎn)單還原,不考慮各種其它邏輯):
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)建,可以簡(jiǎn)單的認(rèn)為就是上述步驟。實(shí)際上對(duì)于一些內(nèi)置類(如Date等),并沒(méi)有這么簡(jiǎn)單,還有一些自己的隱藏邏輯,譬如 [[Class]]標(biāo)識(shí)位等一些重要私有屬性。譬如可以在MDN上看到,以常規(guī)函數(shù)調(diào)用Date(即不加 new 操作符)將會(huì)返回一個(gè)字符串,而不是一個(gè)日期對(duì)象,如果這樣模擬的話會(huì)無(wú)效。
覺(jué)得看起來(lái)比較繁瑣?可以看下圖梳理:
那現(xiàn)在再回頭看看。
什么是構(gòu)造函數(shù)?
如上述中的 MyClass就是一個(gè)構(gòu)造函數(shù),在內(nèi)部它構(gòu)造出了 instance對(duì)象。
什么是實(shí)例對(duì)象?
instance就是一個(gè)實(shí)例對(duì)象,它是通過(guò) new出來(lái)的?
實(shí)例與構(gòu)造的關(guān)系
有時(shí)候淺顯點(diǎn),可以認(rèn)為構(gòu)造函數(shù)是xxx就是xxx的實(shí)例。即:
let instance = new MyClass();
此時(shí)我們就可以認(rèn)為 instance是 MyClass的實(shí)例,因?yàn)樗臉?gòu)造函數(shù)就是它。
不一定,我們那ES5黑魔法來(lái)做示例。
function MyDate() {
// bind屬于Function.prototype,接收的參數(shù)是:object, param1, params2...
var dateInst = new(Function.prototype.bind.apply(Date, [Date].concat(Array.prototype.slice.call(arguments))))();// 更改原型指向,否則無(wú)法調(diào)用MyDate原型上的方法
// ES6方案中,這里就是[[prototype]]這個(gè)隱式原型對(duì)象,在沒(méi)有標(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的實(shí)例。
但是,實(shí)際上, instance卻是由 Date構(gòu)造的,我們可以繼續(xù)用 ES6中的 new.target來(lái)驗(yàn)證。
注意?:關(guān)于 new.target, MDN中的定義是:new.target返回一個(gè)指向構(gòu)造方法或函數(shù)的引用。
嗯哼,也就是說(shuō),返回的是構(gòu)造函數(shù)。
我們可以在相應(yīng)的構(gòu)造中測(cè)試打?。?/p>
class MyDate extends Date {
constructor() {
super();
this.abc = 1;
console.log('~~~new.target.name:MyDate~~~~');
console.log(new.target.name);
}
}// new操作時(shí)的打印結(jié)果是:
// ~~~new.target.name:MyDate~~~~
// MyDate
然后,可以在上面的示例中看到,就算是ES6的Class繼承, MyDate構(gòu)造中打印 new.target也顯示 MyDate,但實(shí)際上它是由 Date來(lái)構(gòu)造(有著 Date關(guān)鍵的 [[Class]]標(biāo)志,因?yàn)槿绻皇荄ate構(gòu)造(如沒(méi)有標(biāo)志)是無(wú)法調(diào)用Date的方法的)。
這也算是一次小小的勘誤吧。
所以,實(shí)際上用 new.target是無(wú)法判斷實(shí)例對(duì)象到底是由哪一個(gè)構(gòu)造構(gòu)造的(這里指的是判斷底層真正的 [[Class]]標(biāo)志來(lái)源的構(gòu)造)。
再回到結(jié)論:實(shí)例對(duì)象不一定就是由它的原型上的構(gòu)造函數(shù)構(gòu)造的,有可能構(gòu)造函數(shù)內(nèi)部有著寄生等邏輯,偷偷的用另一個(gè)函數(shù)來(lái)構(gòu)造了下,當(dāng)然,簡(jiǎn)單情況下,我們直接說(shuō)實(shí)例對(duì)象由對(duì)應(yīng)構(gòu)造函數(shù)構(gòu)造也沒(méi)錯(cuò)(不過(guò),在涉及到這種Date之類的分析時(shí),我們還是得明白)。
這一部分為補(bǔ)充內(nèi)容。
前文中一直提到一個(gè)概念:Date內(nèi)部的 [[Class]]標(biāo)識(shí)。
其實(shí),嚴(yán)格來(lái)說(shuō),不能這樣泛而稱之(前文中只是用這個(gè)概念是為了降低復(fù)雜度,便于理解),它可以分為以下兩部分:
在ES5中,每種內(nèi)置對(duì)象都定義了 [[Class]] 內(nèi)部屬性的值,[[Class]] 內(nèi)部屬性的值用于內(nèi)部區(qū)分對(duì)象的種類
Object.prototype.toString訪問(wèn)的就是這個(gè)[[Class]]
規(guī)范中除了通過(guò) Object.prototype.toString,沒(méi)有提供任何手段使程序訪問(wèn)此值。
而且Object.prototype.toString輸出無(wú)法被修改
而在ES5中,之前的 [[Class]] 不再使用,取而代之的是一系列的 internalslot
Internal slot 對(duì)應(yīng)于與對(duì)象相關(guān)聯(lián)并由各種ECMAScript規(guī)范算法使用的內(nèi)部狀態(tài),它們沒(méi)有對(duì)象屬性,也不能被繼承
根據(jù)具體的 Internal slot 規(guī)范,這種狀態(tài)可以由任何ECMAScript語(yǔ)言類型或特定ECMAScript規(guī)范類型值的值組成
通過(guò) Object.prototype.toString,仍然可以輸出Internal slot值
簡(jiǎn)單點(diǎn)理解(簡(jiǎn)化理解),Object.prototype.toString的流程是:如果是基本數(shù)據(jù)類型(除去Object以外的幾大類型),則返回原本的slot,如果是Object類型(包括內(nèi)置對(duì)象以及自己寫的對(duì)象),則調(diào)用 Symbol.toStringTag。 Symbol.toStringTag方法的默認(rèn)實(shí)現(xiàn)就是返回對(duì)象的Internal slot,這個(gè)方法可以被重寫
這兩點(diǎn)是有所差異的,需要區(qū)分(不過(guò)簡(jiǎn)單點(diǎn)可以統(tǒng)一理解為內(nèi)置對(duì)象內(nèi)部都有一個(gè)特殊標(biāo)識(shí),用來(lái)區(qū)分對(duì)應(yīng)類型-不符合類型就不給調(diào)用)。
JS內(nèi)置對(duì)象是這些:
"Arguments", "Array", "Boolean", "Date", "Error", "Function", "JSON", "Math", "Number", "Object", "RegExp", "String"
ES6新增的一些,這里未提到:(如Promise對(duì)象可以輸出 [objectPromise]),而前文中提到的:
Object.defineProperty(date, Symbol.toStringTag, {
get: function() {
return "Date";
}
});
它的作用是重寫Symbol.toStringTag,截取date(雖然是內(nèi)置對(duì)象,但是仍然屬于Object)的 Object.prototype.toString的輸出,讓這個(gè)對(duì)象輸出自己修改后的 [objectDate]。
但是,僅僅是做到輸出的時(shí)候變成了Date,實(shí)際上內(nèi)部的 internalslot值并沒(méi)有被改變,因此仍然不被認(rèn)為是Date。
其實(shí),在判斷繼承時(shí),沒(méi)有那么多的技巧,就只有關(guān)鍵的一點(diǎn): [[prototype]]( __ptoto__)的指向關(guān)系。
譬如:
console.log(instance instanceof SubClass);
console.log(instance instanceof SuperClass);
實(shí)質(zhì)上就是:
SubClass.prototype是否出現(xiàn)在 instance的原型鏈上
SuperClass.prototype是否出現(xiàn)在 instance的原型鏈上
然后,對(duì)照本文中列舉的一些圖,一目了然就可以看清關(guān)系。有時(shí)候,完全沒(méi)有必要弄的太復(fù)雜。
由于繼承的介紹在網(wǎng)上已經(jīng)多不勝數(shù),因此本文沒(méi)有再重復(fù)描述,而是由一道Date繼承題引發(fā),展開(kāi)(關(guān)鍵就是原型鏈)。
不知道看到這里,各位看官是否都已經(jīng)弄懂了JS中的繼承呢?
另外,遇到問(wèn)題時(shí),多想一想,有時(shí)候你會(huì)發(fā)現(xiàn),其實(shí)你知道的并不是那么多,然后再想一想,又會(huì)發(fā)現(xiàn)其實(shí)并沒(méi)有這么復(fù)雜。

我們?cè)谖⑿派?4小時(shí)期待你的聲音
解答本文疑問(wèn)/技術(shù)咨詢/運(yùn)營(yíng)咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流