国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

從 ++[[]][+[]]+[+[]]==10? 深入淺出弱類型 JS 的隱式轉(zhuǎn)換

miya / 1926人閱讀

摘要:與此相對(duì),強(qiáng)類型語言的類型之間不一定有隱式轉(zhuǎn)換。三為什么是弱類型弱類型相對(duì)于強(qiáng)類型來說類型檢查更不嚴(yán)格,比如說允許變量類型的隱式轉(zhuǎn)換,允許強(qiáng)制類型轉(zhuǎn)換等等。在中,加性運(yùn)算符有大量的特殊行為。

從++[[]][+[]]+[+[]]==10?深入淺出弱類型JS的隱式轉(zhuǎn)換

本文純屬原創(chuàng)? 如有雷同? 純屬抄襲? 不甚榮幸! 歡迎轉(zhuǎn)載!

原文收錄在【我的GitHub博客】,覺得本文寫的不算爛的,可以點(diǎn)擊【我的GitHub博客】順便登錄一下賬號(hào)給個(gè)星星?鼓勵(lì)一下,關(guān)注最新更新動(dòng)態(tài),大家一起多交流學(xué)習(xí),歡迎隨意轉(zhuǎn)載交流,不要錢,文末沒有福利哦?,你懂的?。

如果你很直接,就是直白想看我的結(jié)果分析,請(qǐng)直接跳到[第六章](),只要你看的懂,前面的知識(shí)點(diǎn)可以忽略。

起因

凡是都有一個(gè)來源和起因,這個(gè)題不是我哪篇文章看到的,也不是我瞎幾把亂造出來的,我也沒這個(gè)天賦和能力,是我同事之前丟到群里,叫我們?cè)跒g覽器輸出一下,對(duì)結(jié)果出乎意料,本著實(shí)事求是的精神,探尋事物的本質(zhì),不斷努力追根溯源,總算弄明白了最后的結(jié)果,最后的收獲總算把js的隱式類型轉(zhuǎn)換刨根問底的搞清楚了,也更加深入的明白了為什么JS是弱類型語言了。

題外話

一看就看出答案的大神可以跳過,鄙文會(huì)浪費(fèi)你寶貴的時(shí)間,因?yàn)榇宋臅?huì)很長(zhǎng),涉及到知識(shí)點(diǎn)很多很雜很細(xì),以及對(duì)js源碼的解讀,而且很抽象,如果沒有耐心,也可以直接跳過,本文記錄本人探索這道問題所有的過程,會(huì)很長(zhǎng)。

可能寫的不太清楚,邏輯不太嚴(yán)密,存在些許錯(cuò)誤,還望批評(píng)指正,我會(huì)及時(shí)更正。去年畢業(yè)入坑前端一年,并不是什么老鳥,所以我也是以一個(gè)學(xué)習(xí)者的身份來寫這篇文章,逆向的記錄自己學(xué)習(xí)探索的過程,并不是指點(diǎn)江山,揮斥方遒,目空一切的大神,如果寫的不好,還望見諒。

首先對(duì)于這種問題,有人說是閑的蛋疼,整天研究這些無聊的,有啥用,開發(fā)誰會(huì)這么寫,你鉆牛角尖搞這些有意思嗎?

對(duì)于這種質(zhì)疑,我只能說:愛看不看,反正不是寫給你看。

當(dāng)然,這話也沒錯(cuò),開發(fā)過程中確實(shí)不會(huì)這么寫,但是我們要把開發(fā)和學(xué)習(xí)區(qū)分開來,很多人開發(fā)只為完成事情,不求最好,但求最快,能用就行?。學(xué)習(xí)也是這樣,停留在表面,會(huì)用API就行,不會(huì)去深層次思考原理,因此很難進(jìn)一步提升,就是因?yàn)檫@樣的態(tài)度才誕生了一大批一年經(jīng)驗(yàn)重復(fù)三五年的API大神?。

但是學(xué)習(xí)就不同,學(xué)習(xí)本生就是一個(gè)慢慢深入,尋根問底,追根溯源的過程,如果對(duì)于探尋問題的本質(zhì)追求都沒有,我只能說做做外包就好,探究這種問題對(duì)于開發(fā)確實(shí)沒什么卵用,但是對(duì)我們了解JavaScript這門語言卻有極大的幫助,可以讓我們對(duì)這門語言的了解更上一個(gè)臺(tái)階。JavaScript為什么是弱類型語言,主要體現(xiàn)在哪里,弱類型轉(zhuǎn)換的機(jī)制又是什么?

有人還是覺得其實(shí)這對(duì)學(xué)習(xí)JS也沒什么多大卵用,我只能說:我就喜歡折騰,你管得著?反正我收獲巨多就夠了。

++[[]][+[]]+[+[]]===10?這個(gè)對(duì)不對(duì),我們先不管,先來看幾個(gè)稍微簡(jiǎn)單的例子,當(dāng)做練習(xí)入手。
?

一、作業(yè)例子:

這幾個(gè)是留給大家的作業(yè),涉及到的知識(shí)點(diǎn)下面我會(huì)先一一寫出來,為什么涉及這些知識(shí)點(diǎn),因?yàn)槲易约阂徊讲讲瓤硬冗^來的,所以知道涉及哪些坑,大家最后按照知識(shí)點(diǎn)一步一步分析,一定可以得出 答案來,列出知識(shí)點(diǎn)之后,我們?cè)賮硪黄鸱治?+[[]][+[]]+[+[]]===10?的正確性。

{}+{}//chrome:"object Object",F(xiàn)irfox:NaN

{}+[]//0

[]+{}//"[object Object]"

首先,關(guān)于1、2和3這三個(gè)的答案我是有一些疑惑,先給出答案,希望大家看完這篇文章能和我討論一下自己的想法,求同存異。

4.{}+1

5.({}+1)

6.1+{}

7.[]+1

8.1+[]

9.1-[]

10.1-{}

11.1-!{}

12.1+!{}

13.1+"2"+"2"

14.1+ +"2"+"2"

15.1++"2"+"2"

16.[]==![]

17.[]===![]

這幾個(gè)例子是我隨便寫的,幾乎包含了所有弱類型轉(zhuǎn)換所遇到的坑,為什么會(huì)出現(xiàn)這種情況,就不得不從JS這門語言的特性講起,大家都知道JS是一門動(dòng)態(tài)的弱類型語言,那么你有沒有想過什么叫做弱類型?什么叫做動(dòng)態(tài)?大家都知道這個(gè)概念,但有沒有進(jìn)一步思考呢?

今天通過這幾個(gè)例子就來了解一下JS的弱類型,什么是動(dòng)態(tài)暫時(shí)不做探討。
??

二、強(qiáng)弱類型的判別

按照計(jì)算機(jī)語言的類型系統(tǒng)的設(shè)計(jì)方式,可以分為強(qiáng)類型和弱類型兩種。二者之間的區(qū)別,就在于計(jì)算時(shí)是否可以不同類型之間對(duì)使用者透明地隱式轉(zhuǎn)換。從使用者的角度來看,如果一個(gè)語言可以隱式轉(zhuǎn)換它的所有類型,那么它的變量、表達(dá)式等在參與運(yùn)算時(shí),即使類型不正確,也能通過隱式轉(zhuǎn)換來得到正確地類型,這對(duì)使用者而言,就好像所有類型都能進(jìn)行所有運(yùn)算一樣,所以這樣的語言被稱作弱類型。與此相對(duì),強(qiáng)類型語言的類型之間不一定有隱式轉(zhuǎn)換。
??

三、JS為什么是弱類型?

弱類型相對(duì)于強(qiáng)類型來說類型檢查更不嚴(yán)格,比如說允許變量類型的隱式轉(zhuǎn)換,允許強(qiáng)制類型轉(zhuǎn)換等等。強(qiáng)類型語言一般不允許這么做。具體說明請(qǐng)看維基百科的說明。

根據(jù)強(qiáng)弱類型的判別定義,和上面的十幾個(gè)例子已經(jīng)充分說明JavaScript 是一門弱類型語言了。

?先講一講一些概念,要想弄懂上面題目答案的原理,首先你要徹底弄懂以下的概念,有些時(shí)候?qū)σ恍〇|西似懂非懂,其實(shí)就是對(duì)概念和規(guī)則沒有弄透,弄透之后等會(huì)回過頭對(duì)照就不難理解,不先了解透這些后面的真的不好理解,花點(diǎn)耐心看看,消化一下,最后串通梳理一下,一層一層的往下剝,答案迎刃而解。

? 為了能夠弄明白這種隱式轉(zhuǎn)換是如何進(jìn)行的,我們首先需要搞懂如下一些基礎(chǔ)知識(shí)。如果沒有耐心,直接跳到后面第四章[4.8 小結(jié)]()我總結(jié)的幾條結(jié)論,這里僅給想要一步步通過過程探尋結(jié)果的人看。

??

四、ECMAScript的運(yùn)算符、{}解析、自動(dòng)分號(hào)插入 4.1 ECMAScript 運(yùn)算符優(yōu)先級(jí)
運(yùn)算符 描述
. [] () 字段訪問、數(shù)組下標(biāo)、函數(shù)調(diào)用以及表達(dá)式分組
++ — - + ~ ! delete new typeof void 一元運(yùn)算符、返回?cái)?shù)據(jù)類型、對(duì)象創(chuàng)建、未定義值
* / % 乘法、除法、取模
+ - + 加法、減法、字符串連接
<< >> >>> 移位
< <= > >= instanceof 小于、小于等于、大于、大于等于、instanceof
== != === !== 等于、不等于、嚴(yán)格相等、非嚴(yán)格相等
& 按位與
^ 按位異或
按位或
&& 邏輯與
邏輯或
?: 條件
= oP= 賦值、運(yùn)算賦值
, 多重求值
4.2 ECMAScript 一元運(yùn)算符(+、-)

一元運(yùn)算符只有一個(gè)參數(shù),即要操作的對(duì)象或值。它們是 ECMAScript 中最簡(jiǎn)單的運(yùn)算符。

deletevoid--++這里我們先不扯,免得越扯越多,防止之前博文的啰嗦,這里咋們只講重點(diǎn),有興趣的可以看看w3school(點(diǎn)我查看)對(duì)這幾個(gè)的詳細(xì)講解。

上面的例子我們一個(gè)一個(gè)看,看一個(gè)總結(jié)一個(gè)規(guī)則,基本規(guī)則上面例子幾乎都包含了,如有遺漏,還望反饋補(bǔ)上。

這里我們只講 一元加法一元減法

我們先看看ECMAScript5規(guī)范(熟讀規(guī)范,你會(huì)學(xué)到很多很多)對(duì)一元加法和一元減法的解讀,我們翻到11.4.6和11.4.7。

其中涉及到幾個(gè)ECMAScript定義的抽象操作,ToNumber(x),ToPrimitive(x)等等 下一章詳細(xì)解答,下面出現(xiàn)的抽象定義也同理,先不管這個(gè),有基礎(chǔ)想深入了解可以提前熟讀ECMAScript5規(guī)范(點(diǎn)擊查看)。

規(guī)范本來就是抽象的東西,不太好懂不要緊,我們看看例子,這里的規(guī)范我們只當(dāng)做一種依據(jù)來證明這些現(xiàn)象。

大多數(shù)人都熟悉一元加法和一元減法,它們?cè)?ECMAScript 中的用法與您高中數(shù)學(xué)中學(xué)到的用法相同。
一元加法本質(zhì)上對(duì)數(shù)字無任何影響:

var iNum = 20;
iNum = +iNum;//注意不要和iNum += iNum搞混淆了;
alert(iNum);    //輸出 "20"

盡管一元加法對(duì)數(shù)字無作用,但對(duì)字符串卻有有趣的效果,會(huì)把字符串轉(zhuǎn)換成數(shù)字。

var sNum = "20";
alert(typeof sNum); //輸出 "string"
var iNum = +sNum;
alert(typeof iNum); //輸出 "number"

這段代碼把字符串 "20" 轉(zhuǎn)換成真正的數(shù)字。當(dāng)一元加法運(yùn)算符對(duì)字符串進(jìn)行操作時(shí),它計(jì)算字符串的方式與 parseInt() 相似,主要的不同是只有對(duì)以 "0x" 開頭的字符串(表示十六進(jìn)制數(shù)字),一元運(yùn)算符才能把它轉(zhuǎn)換成十進(jìn)制的值。因此,用一元加法轉(zhuǎn)換 "010",得到的總是 10,而 "0xB" 將被轉(zhuǎn)換成 11。

另一方面,一元減法就是對(duì)數(shù)值求負(fù)(例如把 20 轉(zhuǎn)換成 -20):

var iNum = 20;
iNum = -iNum;
alert(iNum);    //輸出 "-20"

與一元加法運(yùn)算符相似,一元減法運(yùn)算符也會(huì)把字符串轉(zhuǎn)換成近似的數(shù)字,此外還會(huì)對(duì)該值求負(fù)。例如:

var sNum = "20";
alert(typeof sNum); //輸出 "string"
var iNum = -sNum;
alert(iNum);        //輸出 "-20"
alert(typeof iNum); //輸出 "number"

在上面的代碼中,一元減法運(yùn)算符將把字符串 "-20" 轉(zhuǎn)換成 -20(一元減法運(yùn)算符對(duì)十六進(jìn)制和十進(jìn)制的處理方式與一元加法運(yùn)算符相似,只是它還會(huì)對(duì)該值求負(fù))。
??

4.3 ECMAScript 加法運(yùn)算符(+)

在多數(shù)程序設(shè)計(jì)語言中,加性運(yùn)算符(即加號(hào)或減號(hào))通常是最簡(jiǎn)單的數(shù)學(xué)運(yùn)算符。
在 ECMAScript 中,加性運(yùn)算符有大量的特殊行為。

我們還是先看看ECMAScript5規(guī)范(熟讀規(guī)范,你會(huì)學(xué)到很多很多)對(duì)加號(hào)運(yùn)算符 ( + )解讀,我們翻到11.6.1。

前面讀不懂不要緊,下一章節(jié)會(huì)為大家解讀這些抽象詞匯,大家不要慌,但是第七條看的懂吧, 這就是為什么1+"1"="11"而不等于2的原因 ,因?yàn)橐?guī)范就是這樣的,瀏覽器沒有思維只會(huì)按部就班的執(zhí)行規(guī)則,所以規(guī)則是這樣定義的,所以最后的結(jié)果就是規(guī)則規(guī)定的結(jié)果,知道規(guī)則之后,對(duì)瀏覽器一切運(yùn)行的結(jié)果都會(huì)豁然開朗,哦,原來是這樣的啊。

在處理特殊值時(shí),ECMAScript 中的加法也有一些特殊行為:

某個(gè)運(yùn)算數(shù)是 NaN,那么結(jié)果為 NaN。

-Infinity 加 -Infinity,結(jié)果為 -Infinity。

Infinity 加 -Infinity,結(jié)果為 NaN。

+0 加 +0,結(jié)果為 +0。

-0 加 +0,結(jié)果為 +0。

-0 加 -0,結(jié)果為 -0。

不過,如果某個(gè)運(yùn)算數(shù)是字符串,那么采用下列規(guī)則:

如果兩個(gè)運(yùn)算數(shù)都是字符串,把第二個(gè)字符串連接到第一個(gè)上。

如果只有一個(gè)運(yùn)算數(shù)是字符串,把另一個(gè)運(yùn)算數(shù)轉(zhuǎn)換成字符串,結(jié)果是兩個(gè)字符串連接成的字符串。

例如:

var result = 5 + 5; //兩個(gè)數(shù)字
alert(result);      //輸出 "10"
var result2 = 5 + "5";  //一個(gè)數(shù)字和一個(gè)字符串
alert(result);      //輸出 "55"

這段代碼說明了加法運(yùn)算符的兩種模式之間的差別。正常情況下,5+5 等于 10(原始數(shù)值),如上述代碼中前兩行所示。不過,如果把一個(gè)運(yùn)算數(shù)改為字符串 "5",那么結(jié)果將變?yōu)?"55"(原始的字符串值),因?yàn)榱硪粋€(gè)運(yùn)算數(shù)也會(huì)被轉(zhuǎn)換為字符串。

注意:為了避免 JavaScript 中的一種常見錯(cuò)誤,在使用加法運(yùn)算符時(shí),一定要仔細(xì)檢查運(yùn)算數(shù)的數(shù)據(jù)類型
??

4.4 ECMAScript 減法運(yùn)算符(-)

減法運(yùn)算符(-),也是一個(gè)常用的運(yùn)算符:

var iResult = 2 - 1;

減、乘和除沒有加法特殊,都是一個(gè)性質(zhì),這里我們就多帶帶解讀減法運(yùn)算符(-)

我們還是先看看ECMAScript5規(guī)范(熟讀規(guī)范,你會(huì)學(xué)到很多很多)對(duì)減號(hào)運(yùn)算符 ( - )解讀,我們翻到11.6.2。

與加法運(yùn)算符一樣,在處理特殊值時(shí),減法運(yùn)算符也有一些特殊行為:

某個(gè)運(yùn)算數(shù)是 NaN,那么結(jié)果為 NaN。

Infinity 減 Infinity,結(jié)果為 NaN。

-Infinity 減 -Infinity,結(jié)果為 NaN。

Infinity 減 -Infinity,結(jié)果為 Infinity。

-Infinity 減 Infinity,結(jié)果為 -Infinity。

+0 減 +0,結(jié)果為 +0。

-0 減 -0,結(jié)果為 -0。

+0 減 -0,結(jié)果為 +0。

某個(gè)運(yùn)算符不是數(shù)字,那么結(jié)果為 NaN。

注釋:如果運(yùn)算數(shù)都是數(shù)字,那么執(zhí)行常規(guī)的減法運(yùn)算,并返回結(jié)果。

??

4.5 ECMAScript 前自增運(yùn)算符(++)

直接從 C(和 Java)借用的兩個(gè)運(yùn)算符是前增量運(yùn)算符和前減量運(yùn)算符。
所謂前增量運(yùn)算符,就是數(shù)值上加 1,形式是在變量前放兩個(gè)加號(hào)(++):

var iNum = 10;
++iNum;

第二行代碼把 iNum 增加到了 11,它實(shí)質(zhì)上等價(jià)于:

var iNum = 10;
iNum = iNum + 1;

我們還是先看看ECMAScript5規(guī)范(熟讀規(guī)范,你會(huì)學(xué)到很多很多)對(duì)前自增運(yùn)算符 ( ++ )解讀,我們翻到11.4.4。

此圖有坑,后面會(huì)說到,坑了我很久。。。

看不懂這些抽象函數(shù)和詞匯也不要緊,想要深入了解可以通讀ECMAScript5規(guī)范中文版,看幾遍就熟悉了,第一次看見這些肯定一臉懵逼,這是什么玩意,我們只要明白++是干什么就行,這里不必去深究v8引擎怎么實(shí)現(xiàn)這個(gè)規(guī)范的。
至于

var a=1;
console.log(a++);//1
var b=1;
cosole.log(++b);//2

還弄不明白的該好好補(bǔ)習(xí)了,這里不在本文的知識(shí)點(diǎn),也不去花篇幅講解這些,這里我們只要明白一點(diǎn): 所謂前增量運(yùn)算符,就是數(shù)值上加 1
??

4.6 ECMAScript 自動(dòng)分號(hào)(;)插入

盡管 JavaScript 有 C 的代碼風(fēng)格,但是它不強(qiáng)制要求在代碼中使用分號(hào),實(shí)際上可以省略它們。

JavaScript 不是一個(gè)沒有分號(hào)的語言,恰恰相反上它需要分號(hào)來就解析源代碼。 因此 JavaScript 解析器在遇到由于缺少分號(hào)導(dǎo)致的解析錯(cuò)誤時(shí),會(huì)自動(dòng)在源代碼中插入分號(hào)。

4.6.1例子
var foo = function() {
} // 解析錯(cuò)誤,分號(hào)丟失
test()

自動(dòng)插入分號(hào),解析器重新解析。

var foo = function() {
}; // 沒有錯(cuò)誤,解析繼續(xù)
test()
4.6.2工作原理

下面的代碼沒有分號(hào),因此解析器需要自己判斷需要在哪些地方插入分號(hào)。

(function(window, undefined) {
    function test(options) {
        log("testing!")

        (options.list || []).forEach(function(i) {

        })

        options.value.test(
            "long string to pass here",
            "and another long string to pass"
        )

        return
        {
            foo: function() {}
        }
    }
    window.test = test

})(window)

(function(window) {
    window.someLibrary = {}
})(window)

下面是解析器"猜測(cè)"的結(jié)果。

(function(window, undefined) {
    function test(options) {

        // 沒有插入分號(hào),兩行被合并為一行
        log("testing!")(options.list || []).forEach(function(i) {

        }); // <- 插入分號(hào)

        options.value.test(
            "long string to pass here",
            "and another long string to pass"
        ); // <- 插入分號(hào)

        return; // <- 插入分號(hào), 改變了 return 表達(dá)式的行為
        { // 作為一個(gè)代碼段處理
            foo: function() {}
        }; // <- 插入分號(hào)
    }
    window.test = test; // <- 插入分號(hào)

// 兩行又被合并了
})(window)(function(window) {
    window.someLibrary = {}; // <- 插入分號(hào)
})(window); //<- 插入分號(hào)

解析器顯著改變了上面代碼的行為,在另外一些情況下也會(huì)做出錯(cuò)誤的處理。

4.6.3 ECMAScript對(duì)自動(dòng)分號(hào)插入的規(guī)則

我們翻到7.9章節(jié),看看其中插入分號(hào)的機(jī)制和原理,清楚只寫以后就可以盡量以后少踩坑

必須用分號(hào)終止某些 ECMAScript 語句 ( 空語句 , 變量聲明語句 , 表達(dá)式語句 , do-while 語句 , continue 語句 , break 語句 , return 語句 ,throw 語句 )。這些分號(hào)總是明確的顯示在源文本里。然而,為了方便起見,某些情況下這些分號(hào)可以在源文本里省略。描述這種情況會(huì)說:這種情況下給源代碼的 token 流自動(dòng)插入分號(hào)。??

還是比較抽象,看不太懂是不是,不要緊,我們看看實(shí)際例子,總結(jié)出幾個(gè)規(guī)律就行,我們先不看抽象的,看著頭暈,看看具體的總結(jié)說明, 化抽象為具體

首先這些規(guī)則是基于兩點(diǎn):

以換行為基礎(chǔ);

解析器會(huì)盡量將新行并入當(dāng)前行,當(dāng)且僅當(dāng)符合ASI規(guī)則時(shí)才會(huì)將新行視為獨(dú)立的語句。

4.6.3.1 ASI的規(guī)則

1. 新行并入當(dāng)前行將構(gòu)成非法語句,自動(dòng)插入分號(hào)。

if(1 < 10) a = 1
console.log(a)
// 等價(jià)于
if(1 < 10) a = 1;
console.log(a);

2. 在continue,return,break,throw后自動(dòng)插入分號(hào)

return
{a: 1}
// 等價(jià)于
return;
{a: 1};

3. ++、--后綴表達(dá)式作為新行的開始,在行首自動(dòng)插入分號(hào)

a
++
c
// 等價(jià)于
a;
++c;

4. 代碼塊的最后一個(gè)語句會(huì)自動(dòng)插入分號(hào)

function(){ a = 1 }
// 等價(jià)于
function(){ a = 1; }
4.6.3.2 No ASI的規(guī)則

1. 新行以 ( 開始

var a = 1
var b = a
(a+b).toString()
// 會(huì)被解析為以a+b為入?yún)⒄{(diào)用函數(shù)a,然后調(diào)用函數(shù)返回值的toString函數(shù)
var a = 1
var b =a(a+b).toString()

2. 新行以 [ 開始

var a = ["a1", "a2"]
var b = a
[0,1].slice(1)
// 會(huì)被解析先獲取a[1],然后調(diào)用a[1].slice(1)。
// 由于逗號(hào)位于[]內(nèi),且不被解析為數(shù)組字面量,而被解析為運(yùn)算符,而逗號(hào)運(yùn)算符會(huì)先執(zhí)
行左側(cè)表達(dá)式,然后執(zhí)行右側(cè)表達(dá)式并且以右側(cè)表達(dá)式的計(jì)算結(jié)果作為返回值
var a = ["a1", "a2"]
var b = a[0,1].slice(1)

3. 新行以 / 開始

var a = 1
var b = a
/test/.test(b)
// /會(huì)被解析為整除運(yùn)算符,而不是正則表達(dá)式字面量的起始符號(hào)。瀏覽器中會(huì)報(bào)test前多了個(gè).號(hào)
var a = 1
var b = a / test / .test(b)

4. 新行以 + 、 - 、 % 和 * 開始

var a = 2
var b = a
+a
// 會(huì)解析如下格式
var a = 2
var b = a + a

5. 新行以 , 或 . 開始

var a = 2
var b = a
.toString()
console.log(typeof b)
// 會(huì)解析為
var a = 2
var b = a.toString()
console.log(typeof b)

到這里我們已經(jīng)對(duì)ASI的規(guī)則有一定的了解了,另外還有一樣有趣的事情,就是“空語句”。

// 三個(gè)空語句
;;;

// 只有if條件語句,語句塊為空語句。
// 可實(shí)現(xiàn)unless條件語句的效果
if(1>2);else
  console.log("2 is greater than 1 always!");

// 只有while條件語句,循環(huán)體為空語句。
var a = 1
while(++a < 100);
4.6.4 結(jié)論

建議絕對(duì)不要省略分號(hào),同時(shí)也提倡將花括號(hào)和相應(yīng)的表達(dá)式放在一行, 對(duì)于只有一行代碼的 if 或者 else 表達(dá)式,也不應(yīng)該省略花括號(hào)。 這些良好的編程習(xí)慣不僅可以提到代碼的一致性,而且可以防止解析器改變代碼行為的錯(cuò)誤處理。
?關(guān)于JavaScript 語句后應(yīng)該加分號(hào)么?(點(diǎn)我查看)我們可以看看知乎上大牛們對(duì)著個(gè)問題的看法。

???

4.7 ECMAScript 對(duì){}的解讀,確切說應(yīng)該是瀏覽器對(duì){}的解析

js引擎是如何判斷{}是代碼塊還是對(duì)象的?

這個(gè)問題不知道大家有沒有想過,先看看幾個(gè)例子吧?

首先要深入明白的概念:

4.7.1 JavaScript的語句與原始表達(dá)式

原始表達(dá)式是表達(dá)式的最小單位——它不再包含其他表達(dá)式。javascript中的原始表達(dá)式包括this關(guān)鍵字、標(biāo)識(shí)符引用、字面量引用、數(shù)組初始化、對(duì)象初始化和分組表達(dá)式,復(fù)雜表達(dá)式暫不做討論。

語句沒有返回值,而表達(dá)式都有返回值的,表達(dá)式?jīng)]有設(shè)置返回值的話默認(rèn)返回都是undefined。

javascript 里面滿足這個(gè)條件的就函數(shù)聲明、變量聲明(var a=10是聲明和賦值)、for語句、if語句、while語句、switch語句、return、try catch。
但是 javascript 還有一種函數(shù)表達(dá)式,它的形式跟函數(shù)聲明一模一樣。如果寫 function fn() { return 0;} 是函數(shù)聲明而寫var a = function fn(){ return 0;} 等號(hào)后面的就是函數(shù)表達(dá)式。

4.7.2 再來看看幾個(gè)例子吧
1.{a:1}
2.{a:1};
3.{a:1}+1

我們直接在chrome看看結(jié)果:

很奇怪是吧:
再來看看在Firefox下面的情況:

其他IE沒測(cè),我Mac沒法裝IE,大家自行測(cè)試。

第二個(gè)很好理解,在有;的情況下,chrome和Firefox一致的把{a:1}解析為代碼塊,那么{a:1}怎么理解這個(gè)代碼塊,為什么不報(bào)錯(cuò),還記得goto語句嗎,JavaScript保留了goto的語法,我最先也半天沒緩過神來,還好記得c語言里面的這個(gè)語法,沒白學(xué),其實(shí)可以這么理解:

{
a:
 1;
};

關(guān)于第二個(gè){a:1}+1的答案,chrome和Firefox接過也一致,兩個(gè)瀏覽器都會(huì)把這段代碼解析成:

{
a:
 1;
};
+1;

其中關(guān)于{a:1}兩個(gè)瀏覽器就達(dá)成了不一樣的意見,只要{}前面沒有任何運(yùn)算符號(hào),F(xiàn)irefox始終如一的把{}解析成{};也就是我們熟知的代碼塊,而不是對(duì)象字面量。

而chrome就不同了,如果{a:1}后面和前面啥也沒有,{a:1}在chrome瀏覽器會(huì)首先檢查這個(gè)是不是標(biāo)準(zhǔn)對(duì)象格式,如果是返回這個(gè)對(duì)象,如果不是,則當(dāng)做代碼塊執(zhí)行代碼。當(dāng)然這種情況基本可以不考慮,你寫代碼就寫個(gè){a:1}然后就完了?

共同的特點(diǎn):

當(dāng){}的前面有運(yùn)算符號(hào)的時(shí)候,+,-,*,/,()等等,{}都會(huì)被解析成對(duì)象字面量,這無可爭(zhēng)議。

當(dāng){}前面沒有運(yùn)算符時(shí)候但有;結(jié)尾的時(shí)候,或者瀏覽器的自動(dòng)分號(hào)插入機(jī)制給{}后面插入分號(hào)(;)時(shí)候,此時(shí){}都會(huì)被解析成代碼塊。

如果{}前面什么運(yùn)算符都沒有,{}后面也沒有分號(hào)(;)結(jié)尾,F(xiàn)irefox會(huì)始終如一的解析為代碼塊,而chrome有細(xì)微的差別,chrome會(huì)解析為對(duì)象字面量。

這里也是我通過瀏覽器輸出結(jié)果進(jìn)行的一種歸納,當(dāng)然可能還有沒有總結(jié)到位的地方,也可能還有錯(cuò)誤,發(fā)現(xiàn)ECMAScript規(guī)范對(duì)于{}何時(shí)解析為對(duì)象何時(shí)解析為代碼塊也沒有找到比較詳細(xì)的解答,有可能也是我看的不仔細(xì),遺漏了這塊,還望大家能解答一下這塊。

4.8 小結(jié)

如果你覺得以上的很繁瑣,我是新手,也看不太懂,不要緊,不要慌,循序漸進(jìn),以后會(huì)懂的,這里我就直接總結(jié)出幾個(gè)結(jié)論,總結(jié)的不對(duì)的地方,還望反饋指出,對(duì)照著結(jié)論來驗(yàn)證上面的十幾個(gè)例子:

數(shù)組下標(biāo)([])優(yōu)先級(jí)最高, 一元運(yùn)算符(--,++,+,-)的優(yōu)先級(jí)高于加法或減法運(yùn)算符(+,-);

++前增量運(yùn)算符,就是數(shù)值上加 1;

一元運(yùn)算符(+,-)的后面如果不是數(shù)字,會(huì)調(diào)用 ToNumber 方法按照規(guī)則轉(zhuǎn)化成數(shù)字類型。

對(duì)于加號(hào)運(yùn)算符(+)

首先執(zhí)行代碼,調(diào)用 **ToPrimitive** 方法得到原始值

①如果原始值是兩個(gè)數(shù)字,則直接相加得出結(jié)果。

②如果兩個(gè)原始值都是字符串,把第二個(gè)字符串連接到第一個(gè)上,也就是相當(dāng)于調(diào)用 concat 方法。
③如果只有一個(gè)原始值是字符串,調(diào)用 ToString 方法把另一個(gè)運(yùn)算數(shù)轉(zhuǎn)換成字符串,結(jié)果是兩個(gè)字符串連接成的字符串。

對(duì)于減號(hào)運(yùn)算符(-)

不知道大家有沒有看到[ECMAScript](http://yanhaijing.com/es5/#about)規(guī)范,這里比+少了一步 **ToPrimitive**  ,所以 **-** 相對(duì)容易理解。

①如果是兩個(gè)數(shù)字,則直接相減得出結(jié)果。

②如果有一個(gè)不是數(shù)字,會(huì)調(diào)用 ToNumber 方法按照規(guī)則轉(zhuǎn)化成數(shù)字類型,然后進(jìn)行相減。

分號(hào)的插入

①新行并入當(dāng)前行將構(gòu)成非法語句,自動(dòng)插入分號(hào)。
②在continue,return,break,throw后自動(dòng)插入分號(hào)
③++、--后綴表達(dá)式作為新行的開始,在行首自動(dòng)插入分號(hào)
④代碼塊的最后一個(gè)語句會(huì)自動(dòng)插入分號(hào)
⑤新行以 ( 、[、、+ 、 - 、,、. % 和 *開始都不會(huì)插入分號(hào)

{}的兩種解讀

①當(dāng){}的前面有運(yùn)算符號(hào)的時(shí)候,+,-,*,/,()等等,{}都會(huì)被解析成對(duì)象字面量,這無可爭(zhēng)議。
②當(dāng){}前面沒有運(yùn)算符時(shí)候但有;結(jié)尾的時(shí)候,或者瀏覽器的自動(dòng)分號(hào)插入機(jī)制給{}后面插入分號(hào)(;)時(shí)候,此時(shí){}都會(huì)被解析成代碼塊。
③如果{}前面什么運(yùn)算符都沒有,{}后面也沒有分號(hào)(;)結(jié)尾,F(xiàn)irefox會(huì)始終如一的解析為代碼塊,而chrome有細(xì)微的差別,chrome會(huì)解析為對(duì)象字面量。

???

五、ECMAScript的規(guī)范定義的抽象操作

前面關(guān)于ECMAScript規(guī)范的解讀,涉及到幾個(gè)重要的抽象操作:

GetValue(v) : 引用規(guī)范類型

Type(x) : 獲取x的類型

ToNumber(x) : 將x轉(zhuǎn)換為Number類型

ToString(x) : 將x轉(zhuǎn)換為String類型

SameValue(x,y) : 計(jì)算非數(shù)字類型x,y是否相同

ToPrimitive(x) : 將x轉(zhuǎn)換為原始值

?

5.1 原始值

首先,讓我們快速的復(fù)習(xí)一下。 在 JavaScript 中,一共有兩種類型的值(ES6的 symbol 暫不做討論):

原始值(primitives)

1. undefined
2. null
3. boolean
4. number
5. string

對(duì)象值(objects)。
除了原始值外,其他的所有值都是對(duì)象類型的值,包括數(shù)組(array)和函數(shù)(function)等。

?

5.2 GetValue(v)

這里的每個(gè)操作都有其嚴(yán)格并復(fù)雜的定義,可以直接查閱ECMA規(guī)范文檔對(duì)其的詳細(xì)說明。
附上在線中文文檔地址:ECMAScript

我們先看看GetValue(v) : 引用規(guī)范類型,下面是ECMAScript規(guī)范的解讀:

這什么鬼,我也不太懂,反正就是關(guān)于引用規(guī)范的一些抽象描述,鄙人才疏學(xué)淺,也不能化抽象為具體的解釋一番,太抽象了,好難啊,功力還不夠,不懂但對(duì)我們解決上面的問題也沒有什么影響,我們只看關(guān)鍵的幾個(gè):

這里我們先看下SameValue()和ToPrimitive()兩個(gè)操作。
??

5.3 SameValue(x,y)

我們還是先看看ECMAScript5規(guī)范(熟讀規(guī)范,你會(huì)學(xué)到很多很多)對(duì) SameValue 方法解讀,我們翻到9.12。

這個(gè)SameValue操作說的就是,如果x,y兩個(gè)值類型相同,但又不同時(shí)是Number類型時(shí)的比較是否相等的操作。
??

5.4 ToPrimitive(input [ , PreferredType])

ToPrimitive() 方法
轉(zhuǎn)換成原始類型方法。

還是來看看 ECMAScript 標(biāo)準(zhǔn)怎么定義 ToPrimitice 方法的:

是不是看了這個(gè)定義,還是一臉懵逼,ToPrimitive這尼瑪什么玩意啊?這不是等于沒說嗎?

再來看看火狐MDN上面文檔的介紹:
JS::ToPrimitive

查了一下資料,上面要說的可以概括成:

ToPrimitive(obj,preferredType)

JS引擎內(nèi)部轉(zhuǎn)換為原始值ToPrimitive(obj,preferredType)函數(shù)接受兩個(gè)參數(shù),第一個(gè)obj為被轉(zhuǎn)換的對(duì)象,第二個(gè)
preferredType為希望轉(zhuǎn)換成的類型(默認(rèn)為空,接受的值為Number或String)

在執(zhí)行ToPrimitive(obj,preferredType)時(shí)如果第二個(gè)參數(shù)為空并且obj為Date的事例時(shí),此時(shí)preferredType會(huì)
被設(shè)置為String,其他情況下preferredType都會(huì)被設(shè)置為Number如果preferredType為Number,ToPrimitive執(zhí)
行過程如
下:
1. 如果obj為原始值,直接返回;
2. 否則調(diào)用 obj.valueOf(),如果執(zhí)行結(jié)果是原始值,返回之;
3. 否則調(diào)用 obj.toString(),如果執(zhí)行結(jié)果是原始值,返回之;
4. 否則拋異常。

如果preferredType為String,將上面的第2步和第3步調(diào)換,即:
1. 如果obj為原始值,直接返回;
2. 否則調(diào)用 obj.toString(),如果執(zhí)行結(jié)果是原始值,返回之;
3. 否則調(diào)用 obj.valueOf(),如果執(zhí)行結(jié)果是原始值,返回之;
4. 否則拋異常。

首先我們要明白 obj.valueOf()obj.toString() 還有原始值分別是什么意思,這是弄懂上面描述的前提之一:

toString用來返回對(duì)象的字符串表示。

var obj = {};
console.log(obj.toString());//[object Object]

var arr2 = [];
console.log(arr2.toString());//""空字符串

var date = new Date();
console.log(date.toString());//Sun Feb 28 2016 13:40:36 GMT+0800 (中國標(biāo)準(zhǔn)時(shí)間)

valueOf方法返回對(duì)象的原始值,可能是字符串、數(shù)值或bool值等,看具體的對(duì)象。

var obj = {
  name: "obj"
};
console.log(obj.valueOf());//Object {name: "obj"}

var arr1 = [1];
console.log(arr1.valueOf());//[1]



var date = new Date();
console.log(date.valueOf());//1456638436303
如代碼所示,三個(gè)不同的對(duì)象實(shí)例調(diào)用valueOf返回不同的數(shù)據(jù)

原始值指的是["Null","Undefined","String","Boolean","Number"]五種基本數(shù)據(jù)類型之一,一開始就提到過。

弄清楚這些以后,舉個(gè)簡(jiǎn)單的例子:

var a={};
ToPrimitive(a)

分析:a是對(duì)象類型但不是Date實(shí)例對(duì)象,所以preferredType默認(rèn)是Number,先調(diào)用a.valueOf()不是原始值,繼續(xù)來調(diào)
用a.toString()得到string字符串,此時(shí)為原始值,返回之.所以最后ToPrimitive(a)得到就是"[object Object]".

如果覺得描述還不好明白,一大堆描述晦澀又難懂,我們用代碼說話:

const toPrimitive = (obj, preferredType="Number") => {
    let Utils = {
        typeOf: function(obj) {
            return Object.prototype.toString.call(obj).slice(8, -1);
        },
        isPrimitive: function(obj) {
            let types = ["Null", "String", "Boolean", "Undefined", "Number"];
            return types.indexOf(this.typeOf(obj)) !== -1;
        }
    };

    if (Utils.isPrimitive(obj)) {
        return obj;
    }

    preferredType = (preferredType === "String" || Utils.typeOf(obj) === "Date") ?
     "String" : "Number";

    if (preferredType === "Number") {
        if (Utils.isPrimitive(obj.valueOf())) {
            return obj.valueOf()
        };
        if (Utils.isPrimitive(obj.toString())) {
            return obj.toString()
        };
    } else {
        if (Utils.isPrimitive(obj.toString())) {
            return obj.toString()
        };
        if (Utils.isPrimitive(obj.valueOf())) {
            return obj.valueOf()
        };
    }
}

var a={};
ToPrimitive(a);//"[object Object]",與上面文字分析的一致

??

5.5 ToNumber(x)

這個(gè)就比ToPrimitive() 方法好理解多了,就是把其他類型按照一定的規(guī)則轉(zhuǎn)化成數(shù)字類型,也就是類似Number()和parseInt()的方法。

還是繼續(xù)看看ECMAScipt規(guī)范中對(duì)于Number的轉(zhuǎn)換

??是不是又看到 ToPrimitive() 方法了,是不是看了上面的就好理解多了,如果ToNumber(x)這個(gè)x是對(duì)象就要調(diào)用ToPrimitive方法返回x的原始值,是不是一下子就串起來了。

??

5.6 ToString(x)

這個(gè)理解起來跟 ToNumber 方法大同小異,還是繼續(xù)看看ECMAScipt規(guī)范中對(duì)于String的轉(zhuǎn)換.

對(duì)數(shù)值類型應(yīng)用 ToString
ToString 運(yùn)算符將數(shù)字 m 轉(zhuǎn)換為字符串格式的給出如下所示:

如果 m 是 NaN,返回字符串 "NaN"。

如果 m 是 +0 或 -0,返回字符串 "0"。

如果 m 小于零,返回連接 "-" 和 ToString (-m) 的字符串。

如果 m 無限大,返回字符串 "Infinity"。

否則,令 n, k, 和 s 是整數(shù),使得 k ≥ 1, 10k-1 ≤ s < 10k,s × 10n-k 的數(shù)字值是 m,且 k 足夠小。要注意的是,k 是 s 在十進(jìn)制表示中的數(shù)字的個(gè)數(shù)。s 不被 10 整除,且s 的至少要求的有效數(shù)字位數(shù)不一定要被這些標(biāo)準(zhǔn)唯一確定。

如果 k ≤ n ≤ 21,返回由 k 個(gè) s 在十進(jìn)制表示中的數(shù)字組成的字符串(有序的,開頭沒有零),后面跟隨字符 "0" 的 n-k 次出現(xiàn)。

如果 0 < n ≤ 21,返回由 s 在十進(jìn)制表示中的、最多 n 個(gè)有效數(shù)字組成的字符串,后面跟隨一個(gè)小數(shù)點(diǎn) ". ",再后面是余下的 k-n 個(gè) s 在十進(jìn)制表示中的數(shù)字。

如果 -6 < n ≤ 0,返回由字符 "0" 組成的字符串,后面跟隨一個(gè)小數(shù)點(diǎn) ". ",再后面是字符 "0" 的 -n 次出現(xiàn),再往后是 k 個(gè) s 在十進(jìn)制表示中的數(shù)字。

否則,如果 k = 1,返回由單個(gè)數(shù)字 s 組成的字符串,后面跟隨小寫字母 "e",根據(jù) n-1 是正或負(fù),再后面是一個(gè)加號(hào) "+" 或減號(hào) "-" ,再往后是整數(shù) abs(n-1) 的十進(jìn)制表示(沒有前置的零)。

返回由 s 在十進(jìn)制表示中的、最多的有效數(shù)字組成的字符串,后面跟隨一個(gè)小數(shù)點(diǎn) ". ",再后面是余下的是 k-1 個(gè) s 在十進(jìn)制表示中的數(shù)字,再往后是小寫字母 "e",根據(jù)n-1 是正或負(fù),再后面是一個(gè)加號(hào) "+ " 或減號(hào) "-" ,再往后是整數(shù) abs(n-1) 的十進(jìn)制表示(沒有前置的零)。

???

六、驗(yàn)證分析++[[]][+[]]+[+[]]==10?

養(yǎng)兵千日,用兵一時(shí)。

了解了這么多深入的基礎(chǔ)知識(shí),該發(fā)揮用武之地了,我已經(jīng)用完洪荒之力了,是時(shí)候表演真正的技術(shù)了。

好像前面忘記講 == 符號(hào)了,不要緊,之前這個(gè)我的上一篇博文已經(jīng)非常詳細(xì)的分析過了,可以看看我的這篇博文(點(diǎn)擊查看)。這里就不花篇幅介紹了,感覺越來越坑,越寫越多。

這里就簡(jiǎn)單說一下,總結(jié)一下==轉(zhuǎn)換規(guī)則:

==運(yùn)算規(guī)則的圖形化表示

1. undefined == null,結(jié)果是true。且它倆與所有其他值比較的結(jié)果都是false。

2. String == Boolean,需要兩個(gè)操作數(shù)同時(shí)轉(zhuǎn)為Number。

3. String/Boolean == Number,需要String/Boolean轉(zhuǎn)為Number。

4. Object == Primitive,需要Object轉(zhuǎn)為Primitive(具體通過valueOf和toString方法)。

瞧見沒有,一共只有4條規(guī)則!是不是很清晰、很簡(jiǎn)單。
1.首先++[[]][+[]]+[+[]]首先拆分一下:

根據(jù) 4.1 ECMAScript 運(yùn)算符優(yōu)先級(jí) 可以這樣拆分:
相當(dāng)于這樣:

(++[[]][+[]])
+
([+[]])
2.先來分析右邊的[+[]]

①先看里面的+[]

根據(jù) 4.2 ECMAScript 一元運(yùn)算符(+、-) 可以知道,一元運(yùn)算符會(huì)調(diào)用 ToNumber 方法把 ToNumber([]) 轉(zhuǎn)化成數(shù)字。

根據(jù) 5.5 ToNumber(x) 的轉(zhuǎn)換規(guī)則,x為[]是數(shù)組對(duì)象,因此會(huì)調(diào)用 ToPrimitive 方法。

根據(jù) 5.4 ToPrimitive(input [ , PreferredType]) 的轉(zhuǎn)換規(guī)則,空數(shù)組先調(diào)用 valueOf() 方法,得到[]不是原始值,繼續(xù)調(diào)用 toString() 方法,得到 ""空字符串

遞歸的調(diào)用之后成了 ToNumber("") ,答案顯而易見,根據(jù) 5.5 ToNumber(x) 的轉(zhuǎn)換規(guī)則對(duì)照?qǐng)D片可以看出ToNumber("")===0。 那么[+[]]就變相的成了[0]

此時(shí)成了(++[[]][+[]])+[0]

3.再來分析左邊邊的++[[]][+[]]

+[]上面已經(jīng)分析出來了,結(jié)果為0,那么此時(shí)就成了++[[]][0]

根據(jù) 4.2 ECMAScript 一元運(yùn)算符(+、-) 可以知道,數(shù)組下標(biāo)的優(yōu)先級(jí)高于一元運(yùn)算符++,那么理所當(dāng)然成了這樣 ++([[]][0]) ,而[[]][0]可以看出數(shù)組下標(biāo)為0也就是第一個(gè)元素,此時(shí)為[],那么最后成了++[].

++[]這是什么鬼?,根據(jù) 4.5 ECMAScript 前自增運(yùn)算符(++) 沒有發(fā)現(xiàn)任何有調(diào)用 ToNumber 的方法,瀏覽器試了一下,果然有問題,報(bào)錯(cuò)啦,到底哪里出問題了呢,為什么走著走著就走偏了。問題出在哪一步呢?

4.分析問題錯(cuò)誤的原因

為什么++([[]][0])在瀏覽器不報(bào)錯(cuò),而++[]報(bào)錯(cuò),我知道問題就出在這一步,但是一直相不出原因,光瞎想是沒用的,沒事繼續(xù)讀讀ECMAScript規(guī)范,然后中文版的并沒有看出什么玩意,最后在github英文版找到原因了。

首先我們?cè)跒g覽器輸出一下++[]

無意之中照著錯(cuò)誤搜,搜到了這個(gè)后綴自增++:

順便看看大同小異的前綴自增++

Increment Operator_操作的第5步PutValue(expr, newValue)要求expr是引用。這就是問題的關(guān)鍵,為什么之前我沒發(fā)現(xiàn),因?yàn)橹拔乙恢笨吹氖侵形陌妫瑏砜纯粗形陌娴慕貓D對(duì)比一下

發(fā)現(xiàn)后面的3,4,5都沒有,我一度以為自己理解錯(cuò)了,為什么這個(gè)規(guī)則沒有調(diào)ToNumber()卻也能得到數(shù)字,原來是翻譯中這塊內(nèi)容遺漏了,我該好好補(bǔ)習(xí)英語了,盡量多看英文文檔。

看到第五條大大的 Call PutValue(expr, newValue).

閱讀es5英文文檔,可以看到_Prefix Increment Operator_操作的第5步PutValue(expr, newValue)要求expr是引用。

我們還是來看看 PutValue 到底是什么定義,這里我們只需要知道++a,這個(gè)a是引用類型才不會(huì)報(bào)Uncaught ReferenceError: Invalid left-hand side expression in postfix operation這個(gè)錯(cuò)誤。

而我們知道[[]][0]是對(duì)象的屬性訪問,而我們知道對(duì)象的屬性訪問返回的是引用,所以可以正確執(zhí)行。
??

5.進(jìn)一步拆分

++[[]][0]可以這么拆分,只要保持引用關(guān)系就行:

var refence=[[]][0];
++refence;

再來進(jìn)一步拆分

var refence=[];
refence=refence+1;

最后就成了

refence=[]+1;

根據(jù) 4.3 ECMAScript 加法運(yùn)算符(+) ,[]+1可以看成是ToPrimitive([])+ToPrimitive(1),根據(jù) 5.4 ToPrimitive(input [ , PreferredType]) 的轉(zhuǎn)換規(guī)則,空數(shù)組先調(diào)用 valueOf() 方法,得到[]不是原始值,繼續(xù)調(diào)用 toString() 方法,得到 "" 空字符串。

于是就成了 ""+1 ,根據(jù) 4.3 ECMAScript 加法運(yùn)算符(+) ,有一個(gè)字符串,另外一個(gè)也會(huì)變成字符串,所以""+1==="1"。所以 ++[[]][0] === "1" ;

好像分析的是這么回事,其實(shí)錯(cuò)了,大家不要跟著我錯(cuò)誤的步驟走,我其實(shí)忽略了很重要的一點(diǎn)。

看看規(guī)范有一點(diǎn)遺漏了,就是 Let oldValue be ToNumber(GetValue(expr)).

就是++時(shí)候舊的值要進(jìn)行 ToNumber() 運(yùn)算,最后最后一步應(yīng)該是這樣子的:

refence=ToNumber([])+1;

ToNumber([])===0,別問我為什么,照著我上面的分析自己分析一遍,不難,我因?yàn)榉治龆嗔耍砸谎劬涂闯鰜砹耍宰詈蟪闪?+1=1的問題,所以 ++[[]][0] === 1
??

6. 謎底揭開?

左邊++[[]][0] === 1;
右邊[+[]]的值為[0];
所以最后成了1+[0]的問題了。
根據(jù) 5.4 ToPrimitive(input [ , PreferredType]) 的轉(zhuǎn)換規(guī)則,[0]數(shù)組先調(diào)用 valueOf() 方法,得到[0]不是原始值,繼續(xù)調(diào)用 toString() 方法,得到 “0” 的字符串。

所以最后就成了 1+"0"==="10"

7.最后的最后

于是最后就成了 "10" == 10 的問題,根據(jù)ECMAScript規(guī)范==的對(duì)應(yīng)法則:

對(duì)比第5條,可以發(fā)現(xiàn)最后成了ToNumber("10")與10的比較,而ToNumber("10") === 10,
左邊最后 === 10,
右邊最后 === 10。
10 === 10為true.

所以 ++[[]][+[]]+[+[]]==10 為true,大功告成,可喜可賀,實(shí)力分析了一波,有錯(cuò)誤還望批評(píng)指正。

??

七、我的疑惑
1. {}+{}//chrome:"[object Object][object Object]",F(xiàn)irfox:NaN

2. {}+[]//0

3. []+{}//"[object Object]"

通過這個(gè)例子,我發(fā)現(xiàn)剛才4.7 ECMAScript 對(duì){}的解讀還不夠徹底,首先我們按照前面常規(guī)的思維來解答:

1.第一種思維:第一個(gè){}解析為對(duì)面字面量

第一例子: {}+{} 首先左邊{}和右邊{}會(huì)調(diào)用 ToPrimitive 兩邊都會(huì)得到:"[object Object]",所以最后就是這兩個(gè)相同的字符串相加,得到: "object Object" ,chrome符合,F(xiàn)irefox不符合。

第二個(gè)例子: {}+[] 按照這種思維首先左邊{}和右邊[]會(huì)調(diào)用 ToPrimitive ,分別得到"[object Object]"和""空字符串,那么相加結(jié)果應(yīng)該是 "[object Object]" ,為什么結(jié)果成了 0 ,而且在 chromeFirfox 都是0?

第三個(gè)例子: []+{} 按照這種思維首先左邊[]和右邊{}會(huì)調(diào)用 ToPrimitive ,分別得到""空字符串和"[object Object]",最后相加結(jié)果 "[object Object]" ,這個(gè)沒有任何疑惑,chrome和Firefox都符合。

2.第一種思維:第一個(gè){}解析為代碼塊

第一例子: {}+{} 瀏覽器這么解析,把{}不解析為對(duì)象字面量而是代碼塊,也就是let a={}這種塊,代碼可以看成是這樣 {};+{} ,那么{};執(zhí)行啥也沒有,接下來就是 +{} ,+是一元運(yùn)算符,上面講到了,這里+{}執(zhí)行時(shí)候首先會(huì)調(diào)用ToNumber(),參數(shù){}是object會(huì)首先調(diào)用 ToPrimitive 得到原始值: "[object Object]" ,這時(shí)候就可以發(fā)現(xiàn)ToNumber("[object Object]")轉(zhuǎn)化的就是 NaN 了,chrome不符合,F(xiàn)irefox符合。

第二個(gè)例子: {}+[] 按照這種思維,最后解析成 {};+[] ,+是一元運(yùn)算符,上面講到了,這里+[]執(zhí)行時(shí)候首先會(huì)調(diào)用ToNumber(),參數(shù)[]是object會(huì)首先調(diào)用 ToPrimitive 得到原始值: ""空字符串 ,最后根據(jù)規(guī)則ToNumber("")得到數(shù)字0.這種思維下沒有任何疑惑,chrome和Firefox都符合。

第三個(gè)例子: []+{} 首先左邊[]和右邊{}會(huì)調(diào)用 ToPrimitive ,分別得到""空字符串和"[object Object]",最后相加結(jié)果 "[object Object]" ,這個(gè)沒有任何疑惑,chrome和Firefox都符合。

?那么問題來了?問題的矛盾就在于第一條和第二條,chrome和Firefox對(duì)于{}+{}解析是不一樣的,對(duì)于第一個(gè){}chrome解析為對(duì)象字面量,而Firefox解析為代碼塊,這無可厚非, 關(guān)鍵是第二個(gè)例子{}+[] ,既然第一個(gè)例子 {}+{} 的第一個(gè){}chrome解析為對(duì)象字面量而第二個(gè)例子 {}+[] 中,chrome卻解析為代碼塊,匪夷所思,有誰能扒一扒源碼分析一下,chrome對(duì){}的詳細(xì)解析,到底什么時(shí)候解析為代碼塊,什么時(shí)候解析為對(duì)象字面量?有點(diǎn)想不明白為什么這么不一致,而 Firefox始終如一,第一個(gè){}一直解析為代碼塊,運(yùn)算符號(hào)后面{}解析為對(duì)象字面量

3.捉摸不透

Firefox的我能理解,開頭{}一律解析為代碼塊(block),而chrome卻讓人捉摸不透。。。
??

八、結(jié)束

前面的十幾個(gè)例子,大家有興趣對(duì)照著規(guī)則自己一個(gè)一個(gè)做做,看看自己是否真的理解了,理解了也再熟悉一遍,學(xué)習(xí)本來就是一個(gè)重復(fù)的過程。

突然靈機(jī)一動(dòng):

    var obj = {
        valueOf: function() {
            return 18;
        }
    };

    console.log(
            1 <= "2",
            "1" <= "a",
            obj >= "17"
    );

這個(gè)答案又是多少呢?

 var obj = {
        valueOf: function() {
            return {a:1};
        },
        toString:function(){
            return 0;
        }
    };

console.log(obj==[]);

最后這個(gè)呢?

 var obj = {
        valueOf: function() {
            return {a:1};
        },
        toString:function(){
            return "0";
        }
    };

console.log(obj==[]);

??

九、相關(guān)資料

從 []==![] 為 true 來剖析 JavaScript 各種蛋疼的類型轉(zhuǎn)換(厚顏無恥的也參考一下自己文章)
ECMAScript5.1中文版
Annotated ECMAScript 5.1
JavaScript 秘密花園
w3school中文文檔
JS魔法堂:ASI(自動(dòng)分號(hào)插入機(jī)制)和前置分號(hào)
JavaScript中,{}+{}等于多少?

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/81896.html

相關(guān)文章

  • JavaScript隱式類型轉(zhuǎn)換

    摘要:所謂裝箱轉(zhuǎn)換,正是把基本類型轉(zhuǎn)換為對(duì)應(yīng)的對(duì)象,他是類型轉(zhuǎn)換中一種相當(dāng)重要的種類。拆箱轉(zhuǎn)換在標(biāo)準(zhǔn)中,規(guī)定了函數(shù),它是對(duì)象類型到基本類型的轉(zhuǎn)換即,拆箱轉(zhuǎn)換。拆箱轉(zhuǎn)換會(huì)嘗試調(diào)用和來獲得拆箱后的基本類型。 JavaScript隱式類型轉(zhuǎn)換 基本數(shù)據(jù)類型 ECMAScript 一共定義了七種 build-in types,其中六種為 Primitive Value,Null, Undefined...

    bingo 評(píng)論0 收藏0
  • JavaScript 中隱式類型轉(zhuǎn)換

    摘要:雖然你可能很驚訝甚至可能懷疑是的但是這都是有語言自己的一個(gè)隱式類型轉(zhuǎn)換的套路。基本的隱式類型轉(zhuǎn)換基本類型的隱式轉(zhuǎn)換這個(gè)其實(shí)我們使用的最多例如結(jié)果返回的是而不是這就是類型的隱式轉(zhuǎn)換。 基本上所有的語言都有 隱式類型轉(zhuǎn)換 ,但是對(duì)于 弱類型語言(JS) 來說 ,隱式類型轉(zhuǎn)換會(huì)比 強(qiáng)類型語言(Java) 帶來更大的副作用,有些行為甚至是不可思議的。雖然你可能很驚訝 ,甚至可能懷疑是 JS 的...

    txgcwm 評(píng)論0 收藏0
  • 17道面試題徹底理解 JavaScript 中的類型轉(zhuǎn)換

    摘要:隱式類型轉(zhuǎn)換通常在邏輯判斷或者有邏輯運(yùn)算符時(shí)被觸發(fā)。一元加號(hào)執(zhí)行字符串的類型轉(zhuǎn)換。邏輯運(yùn)算符和將值轉(zhuǎn)為型,但是會(huì)返回原始值不是。計(jì)算從表達(dá)式開始,該表達(dá)式通過方法轉(zhuǎn)換為空字符串,然后轉(zhuǎn)換為。總結(jié)查看原文關(guān)注每日一道面試題詳解 類型轉(zhuǎn)換是將值從一種類型轉(zhuǎn)換為另一種類型的過程(比如字符串轉(zhuǎn)數(shù)字,對(duì)象轉(zhuǎn)布爾值等)。任何類型不論是原始類型還是對(duì)象類型都可以進(jìn)行類型轉(zhuǎn)換,JavaScript 的...

    SKYZACK 評(píng)論0 收藏0
  • 前端基礎(chǔ)進(jìn)階(八):深入詳解函數(shù)的柯里化

    摘要:函數(shù)被轉(zhuǎn)化之后得到柯里化函數(shù),能夠處理的所有剩余參數(shù)。因此柯里化也被稱為部分求值。那么函數(shù)的柯里化函數(shù)則可以如下因此下面的運(yùn)算方式是等價(jià)的。而這里對(duì)于函數(shù)參數(shù)的自由處理,正是柯里化的核心所在。額外知識(shí)補(bǔ)充無限參數(shù)的柯里化。 showImg(https://segmentfault.com/img/remote/1460000008493346); 柯里化是函數(shù)的一個(gè)比較高級(jí)的應(yīng)用,想要...

    kk_miles 評(píng)論0 收藏0
  • 深入理解JavaScript的類型轉(zhuǎn)換

    摘要:等同于等同于其他類型和布爾類型之間的比較如果是布爾類型,則返回的結(jié)果。 showImg(https://segmentfault.com/img/bVburFq?w=796&h=398); 前言 JavaScript作為一門弱類型語言,我們?cè)诿刻斓木帉懘a過程中,無時(shí)無刻不在應(yīng)用著值類型轉(zhuǎn)換,但是很多時(shí)候我們只是在單純的寫,并不曾停下腳步去探尋過值類型轉(zhuǎn)換的內(nèi)部轉(zhuǎn)換規(guī)則,最近通過閱讀你...

    W4n9Hu1 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

miya

|高級(jí)講師

TA的文章

閱讀更多
最新活動(dòng)
閱讀需要支付1元查看
<