摘要:與此相對(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í)入手。
?
這幾個(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í)不做探討。
??
按照計(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)換。
??
弱類型相對(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)算賦值 | ||
, | 多重求值 |
一元運(yùn)算符只有一個(gè)參數(shù),即要操作的對(duì)象或值。它們是 ECMAScript 中最簡(jiǎn)單的運(yùn)算符。
delete,void,--,++這里我們先不扯,免得越扯越多,防止之前博文的啰嗦,這里咋們只講重點(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ù))。
??
在多數(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ù)類型
??
減法運(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 。
??
盡管 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)。
var foo = function() { } // 解析錯(cuò)誤,分號(hào)丟失 test()
自動(dòng)插入分號(hào),解析器重新解析。
var foo = function() { }; // 沒有錯(cuò)誤,解析繼續(xù) test()
下面的代碼沒有分號(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ò)誤的處理。
我們翻到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ú)立的語句。
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; }
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);
建議絕對(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è)例子吧?
首先要深入明白的概念:
原始表達(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á)式。
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è)操作。
??
我們還是先看看ECMAScript5規(guī)范(熟讀規(guī)范,你會(huì)學(xué)到很多很多)對(duì) SameValue 方法解讀,我們翻到9.12。
這個(gè)SameValue操作說的就是,如果x,y兩個(gè)值類型相同,但又不同時(shí)是Number類型時(shí)的比較是否相等的操作。
??
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í)行。
??
++[[]][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 。
??
左邊++[[]][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 ,而且在 chrome 和 Firfox 都是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卻讓人捉摸不透。。。
??
前面的十幾個(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
摘要:所謂裝箱轉(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...
摘要:雖然你可能很驚訝甚至可能懷疑是的但是這都是有語言自己的一個(gè)隱式類型轉(zhuǎn)換的套路。基本的隱式類型轉(zhuǎn)換基本類型的隱式轉(zhuǎn)換這個(gè)其實(shí)我們使用的最多例如結(jié)果返回的是而不是這就是類型的隱式轉(zhuǎn)換。 基本上所有的語言都有 隱式類型轉(zhuǎn)換 ,但是對(duì)于 弱類型語言(JS) 來說 ,隱式類型轉(zhuǎn)換會(huì)比 強(qiáng)類型語言(Java) 帶來更大的副作用,有些行為甚至是不可思議的。雖然你可能很驚訝 ,甚至可能懷疑是 JS 的...
摘要:隱式類型轉(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 的...
摘要:函數(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)用,想要...
摘要:等同于等同于其他類型和布爾類型之間的比較如果是布爾類型,則返回的結(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ī)則,最近通過閱讀你...
閱讀 1951·2021-09-07 10:24
閱讀 2086·2019-08-30 15:55
閱讀 2037·2019-08-30 15:43
閱讀 669·2019-08-29 15:25
閱讀 1044·2019-08-29 12:19
閱讀 1927·2019-08-23 18:32
閱讀 1515·2019-08-23 17:59
閱讀 946·2019-08-23 12:22