如何繼承 Date 對(duì)象掰烟?由一道題徹底弄懂 JS 繼承

作者:撒網(wǎng)要見(jiàn)魚(yú)

http://www.dailichun.com/2018/01/15/howtoextenddate.html


前言

故事是從一次實(shí)際需求中開(kāi)始的。。纫骑。

某天蝎亚,某人向我尋求了一次幫助,要協(xié)助寫(xiě)一個(gè)日期工具類(lèi)先馆,要求:

1发框、此類(lèi)繼承自?Date,擁有Date的所有屬性和對(duì)象

2煤墙、此類(lèi)可以自由拓展方法

形象點(diǎn)描述梅惯,就是要求可以這樣:

// 假設(shè)最終的類(lèi)是 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)典的組合寄生法寫(xiě)了一個(gè)繼承徙歼,然后,剛準(zhǔn)備完美收工鳖枕,一運(yùn)行魄梯,卻出現(xiàn)了以下的情景:

但是的心情是這樣的: ??囧

以前也沒(méi)有遇到過(guò)類(lèi)似的問(wèn)題,然后自己嘗試著用其它方法宾符,多次嘗試酿秸,均無(wú)果(不算暴力混合法的情況),其實(shí)回過(guò)頭來(lái)看魏烫,是因?yàn)樗悸沸缕胬彼眨瑧{空想不到,并不是原理上有多難哄褒。稀蟋。。

于是呐赡,借助強(qiáng)大的搜素引擎退客,搜集資料,最后链嘀,再自己總結(jié)了一番萌狂,才有了本文。

正文開(kāi)始前怀泊,各位看官可以先暫停往下讀茫藏,嘗試下,在不借助任何網(wǎng)絡(luò)資料的情況下霹琼,是否能實(shí)現(xiàn)上面的需求刷允?(就以 10分鐘為限吧)

分析問(wèn)題的關(guān)鍵

借助stackoverflow上的回答冤留。

經(jīng)典的繼承法有何問(wèn)題

先看看本文最開(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高程(紅寶書(shū))中推薦的一種,一直用糯而,從未失手天通,結(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ò)原型繼承的也不行。

為什么無(wú)法被繼承昌跌?

首先仰禀,看看 MDN上的解釋?zhuān)厦嬗刑岬剑琂avaScript的日期對(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的方法。

該如何實(shí)現(xiàn)繼承碍庵?

雖然原因找到了映企,但是問(wèn)題仍然要解決啊,真的就沒(méi)辦法了么静浴?當(dāng)然不是堰氓,事實(shí)上還是有不少實(shí)現(xiàn)的方法的。

暴力混合法

首先苹享,說(shuō)說(shuō)說(shuō)下暴力的混合法双絮,它是下面這樣子的:

說(shuō)到底就是:內(nèi)部生成一個(gè) Date對(duì)象浴麻,然后此類(lèi)暴露的方法中,把原有 Date中所有的方法都代理一遍囤攀,而且嚴(yán)格來(lái)說(shuō)软免,這根本算不上繼承(都沒(méi)有原型鏈回溯)。

ES5黑魔法

然后焚挠,再看看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)系一目了然)

可以看到,用的是非常巧妙的一種做法:

正常繼承的情況如下:

1硅急、newMyDate()返回實(shí)例對(duì)象?date是由?MyDate構(gòu)造的

2覆享、原型鏈回溯是:?date(MyDate對(duì)象)->date.__proto__->MyDate.prototype->MyDate.prototype.__proto__->Date.prototype

這種做法的繼承的情況如下:

1、newMyDate()返回實(shí)例對(duì)象?date是由?Date構(gòu)造的

2营袜、原型鏈回溯是:?date(Date對(duì)象)->date.__proto__->MyDate.prototype->MyDate.prototype.__proto__->Date.prototype

可以看出撒顿,關(guān)鍵點(diǎn)在于:

3、構(gòu)造函數(shù)里返回了一個(gè)真正的?Date對(duì)象(由?Date構(gòu)造荚板,所以有這些內(nèi)部類(lèi)中的關(guān)鍵?[[Class]]標(biāo)志)凤壁,所以它有調(diào)用?Date原型上方法的權(quán)利

4、構(gòu)造函數(shù)里的Date對(duì)象的?[[ptototype]](對(duì)外跪另,瀏覽器中可通過(guò)?__proto__訪(fǎng)問(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]]

ES6大法

當(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的秒咐,所以效果完全不一樣。

ES6寫(xiě)法碘裕,然后Babel打包

雖然說(shuō)上述ES6大法是可以直接繼承Date的携取,但是,考慮到實(shí)質(zhì)上大部分的生產(chǎn)環(huán)境是: ES6+Babel

直接這樣用ES6 + Babel是會(huì)出問(wèn)題的帮孔。

不信的話(huà)雷滋,可以自行嘗試下,Babel打包成ES5后代碼大致是這樣的:

然后當(dāng)信心滿(mǎn)滿(mǎn)的開(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]]之類(lèi)的私有標(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ò)度迷信)介时。

幾種繼承的細(xì)微區(qū)別

雖然上述提到的三種方法都可以達(dá)到繼承 Date的目的-混合法嚴(yán)格說(shuō)不能算繼承,只不過(guò)是另類(lèi)實(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)分別是:(排出了混合法)

1、ES6的Class大法

2原探、經(jīng)典組合寄生繼承法

3乱凿、本文中的取巧做法顽素,Date構(gòu)造實(shí)例,然后更改?__proto__的那種

4徒蟆、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):

1段审、MyDate們的proto指向不一樣

2全蝶、Object.prototype.toString.call的輸出不一樣

3、對(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)番甩,這里不贅述)侵贵。

ES6繼承與ES5繼承的區(qū)別

從上午中的分析可以看到一點(diǎn):ES6的Class寫(xiě)法繼承是沒(méi)問(wèn)題的。但是換成ES5寫(xiě)法就不行了缘薛。

所以ES6的繼承大法和ES5肯定是有區(qū)別的窍育,那么究竟是哪里不同呢卡睦?(主要是結(jié)合的本文繼承Date來(lái)說(shuō))

區(qū)別:(以 SubClassSuperClass漱抓, instance為例)

ES5中繼承的實(shí)質(zhì)是:(那種經(jīng)典組合寄生繼承法)

1表锻、先由子類(lèi)(?SubClass)構(gòu)造出實(shí)例對(duì)象this

2、然后在子類(lèi)的構(gòu)造函數(shù)中乞娄,將父類(lèi)(?SuperClass)的屬性添加到?this上瞬逊,?SuperClass.apply(this,arguments)

3、子類(lèi)原型(?SubClass.prototype)指向父類(lèi)原型(?SuperClass.prototype

4仪或、所以?instance是子類(lèi)(?SubClass)構(gòu)造出的(所以沒(méi)有父類(lèi)的?[[Class]]關(guān)鍵標(biāo)志)

5确镊、所以,?instance有?SubClass和?SuperClass的所有實(shí)例屬性溶其,以及可以通過(guò)原型鏈回溯骚腥,獲取?SubClass和?SuperClass原型上的方法

ES6中繼承的實(shí)質(zhì)是:

1、先由父類(lèi)(?SuperClass)構(gòu)造出實(shí)例對(duì)象this瓶逃,這也是為什么必須先調(diào)用父類(lèi)的?super()方法(子類(lèi)沒(méi)有自己的this對(duì)象,需先由父類(lèi)構(gòu)造)

2廓块、然后在子類(lèi)的構(gòu)造函數(shù)中厢绝,修改this(進(jìn)行加工),譬如讓它指向子類(lèi)原型(?SubClass.prototype)带猴,這一步很關(guān)鍵昔汉,否則無(wú)法找到子類(lèi)原型(注,子類(lèi)構(gòu)造中加工這一步的實(shí)際做法是推測(cè)出的拴清,從最終效果來(lái)推測(cè))

3靶病、然后同樣,子類(lèi)原型(?SubClass.prototype)指向父類(lèi)原型(?SuperClass.prototype

4口予、所以?instance是父類(lèi)(?SuperClass)構(gòu)造出的(所以有著父類(lèi)的?[[Class]]關(guān)鍵標(biāo)志)

5娄周、所以,?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)置的類(lèi)妻献,如Date蛛株,Array团赁,Error等。如果this先被創(chuàng)建出來(lái)泳挥,在傳給Array等系統(tǒng)內(nèi)置類(lèi)的構(gòu)造函數(shù)然痊,這些內(nèi)置類(lè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)置類(lèi)的方法)

構(gòu)造函數(shù)與實(shí)例對(duì)象

看到這里唆香,不知道是否對(duì)上午中頻繁提到的構(gòu)造函數(shù)實(shí)例對(duì)象有所混淆與困惑呢吨艇?這里稍微描述下躬它。

要弄懂這一點(diǎn),需要先知道 new一個(gè)對(duì)象到底發(fā)生了什么东涡?先形象點(diǎn)說(shuō):

new MyClass()中冯吓,都做了些什么工作

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í)位等)

1祖娘、構(gòu)造函數(shù)內(nèi)部失尖,創(chuàng)建一個(gè)新的對(duì)象,它繼承自?MyClass.prototype渐苏,?letinstance=Object.create(MyClass.prototype);

2掀潮、使用指定的參數(shù)調(diào)用構(gòu)造函數(shù)?MyClass,并將 this綁定到新創(chuàng)建的對(duì)象琼富,?MyClass.call(instance);仪吧,執(zhí)行后擁有所有實(shí)例屬性

3、如果構(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ò)用戶(hù)如果想覆蓋這個(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)置類(lèi)(如Date等),并沒(méi)有這么簡(jiǎn)單闻镶,還有一些自己的隱藏邏輯斥滤,譬如 [[Class]]標(biāo)識(shí)位等一些重要私有屬性将鸵。譬如可以在MDN上看到,以常規(guī)函數(shù)調(diào)用Date(即不加 new 操作符)將會(huì)返回一個(gè)字符串佑颇,而不是一個(gè)日期對(duì)象顶掉,如果這樣模擬的話(huà)會(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)為 instanceMyClass的實(shí)例,因?yàn)樗臉?gòu)造函數(shù)就是它戳粒。

實(shí)例就一定是由對(duì)應(yīng)的構(gòu)造函數(shù)構(gòu)造出的么路狮?

不一定,我們那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)為 instanceMyDate的實(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è)試打优哨濉:

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之類(lèi)的分析時(shí),我們還是得明白)耻警。

[[Class]]與Internal slot

這一部分為補(bǔ)充內(nèi)容隔嫡。

前文中一直提到一個(gè)概念:Date內(nèi)部的 [[Class]]標(biāo)識(shí)

其實(shí)甘穿,嚴(yán)格來(lái)說(shuō)腮恩,不能這樣泛而稱(chēng)之(前文中只是用這個(gè)概念是為了降低復(fù)雜度,便于理解)温兼,它可以分為以下兩部分:

在ES5中秸滴,每種內(nèi)置對(duì)象都定義了 [[Class]] 內(nèi)部屬性的值,[[Class]] 內(nèi)部屬性的值用于內(nèi)部區(qū)分對(duì)象的種類(lèi)

1募判、Object.prototype.toString訪(fǎng)問(wèn)的就是這個(gè)[[Class]]

2荡含、規(guī)范中除了通過(guò)?Object.prototype.toString,沒(méi)有提供任何手段使程序訪(fǎng)問(wèn)此值届垫。

3释液、而且Object.prototype.toString輸出無(wú)法被修改

而在ES5中,之前的 [[Class]] 不再使用敦腔,取而代之的是一系列的 internalslot

4均澳、Internal slot 對(duì)應(yīng)于與對(duì)象相關(guān)聯(lián)并由各種ECMAScript規(guī)范算法使用的內(nèi)部狀態(tài),它們沒(méi)有對(duì)象屬性,也不能被繼承

5找前、根據(jù)具體的 Internal slot 規(guī)范糟袁,這種狀態(tài)可以由任何ECMAScript語(yǔ)言類(lèi)型或特定ECMAScript規(guī)范類(lèi)型值的值組成

6、通過(guò)?Object.prototype.toString躺盛,仍然可以輸出Internal slot值

7项戴、簡(jiǎn)單點(diǎn)理解(簡(jiǎn)化理解),Object.prototype.toString的流程是:如果是基本數(shù)據(jù)類(lèi)型(除去Object以外的幾大類(lèi)型)槽惫,則返回原本的slot周叮,如果是Object類(lèi)型(包括內(nèi)置對(duì)象以及自己寫(xiě)的對(duì)象),則調(diào)用?Symbol.toStringTag界斜。?Symbol.toStringTag方法的默認(rèn)實(shí)現(xiàn)就是返回對(duì)象的Internal slot仿耽,這個(gè)方法可以被重寫(xiě)

這兩點(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)類(lèi)型-不符合類(lèi)型就不給調(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";

? ?}

});

它的作用是重寫(xiě)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ì)上就是:

1打掘、SubClass.prototype是否出現(xiàn)在?instance的原型鏈上

2华畏、SuperClass.prototype是否出現(xiàn)在?instance的原型鏈上

然后,對(duì)照本文中列舉的一些圖尊蚁,一目了然就可以看清關(guān)系亡笑。有時(shí)候,完全沒(méi)有必要弄的太復(fù)雜横朋。

寫(xiě)在最后的話(huà)

由于繼承的介紹在網(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ù)雜畅买。并闲。。

感興趣的小伙伴谷羞,可以關(guān)注公眾號(hào)【grain先森】帝火,回復(fù)關(guān)鍵詞 “vue”,獲取更多資料洒宝,更多關(guān)鍵詞玩法期待你的探索~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末购公,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子雁歌,更是在濱河造成了極大的恐慌宏浩,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件靠瞎,死亡現(xiàn)場(chǎng)離奇詭異比庄,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)乏盐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)佳窑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人父能,你說(shuō)我怎么就攤上這事神凑。” “怎么了何吝?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵溉委,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我爱榕,道長(zhǎng)瓣喊,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任黔酥,我火速辦了婚禮藻三,結(jié)果婚禮上洪橘,老公的妹妹穿的比我還像新娘。我一直安慰自己棵帽,他們只是感情好熄求,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著岖寞,像睡著了一般抡四。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上仗谆,一...
    開(kāi)封第一講書(shū)人閱讀 51,125評(píng)論 1 297
  • 那天指巡,我揣著相機(jī)與錄音,去河邊找鬼隶垮。 笑死藻雪,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的狸吞。 我是一名探鬼主播勉耀,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼蹋偏!你這毒婦竟也來(lái)了便斥?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤威始,失蹤者是張志新(化名)和其女友劉穎枢纠,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體黎棠,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡晋渺,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了脓斩。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片木西。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖随静,靈堂內(nèi)的尸體忽然破棺而出八千,到底是詐尸還是另有隱情,我是刑警寧澤燎猛,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布叼丑,位于F島的核電站,受9級(jí)特大地震影響扛门,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜纵寝,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一论寨、第九天 我趴在偏房一處隱蔽的房頂上張望星立。 院中可真熱鬧,春花似錦葬凳、人聲如沸绰垂。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)劲装。三九已至,卻和暖如春昌简,著一層夾襖步出監(jiān)牢的瞬間占业,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工纯赎, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留谦疾,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓犬金,卻偏偏與公主長(zhǎng)得像念恍,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子晚顷,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容