JavaScript 是一門傳奇性的程式語言。它在 1990 年代中期,僅用 10 天就誕生了,正值瀏覽器大戰期間,當時 Netscape 正試圖對 Microsoft 造成致命一擊。眾所周知,事情並沒有完全照著計畫走……如今,有些人喜歡 JavaScript,有些人討厭它,也有人只是拿它開玩笑。但事實是,沒有它,現代網路根本不可能存在!

多年來,JavaScript 經歷了無數次革命。如今它的能力,幾乎無法與 1990 年代的 JavaScript 相提並論。唯一的小問題是……語言的每一次變動,都必須顧及一件事:外面有數百萬個網站仍然依賴舊版 JavaScript 的行為。而其中有些網站,大概會把那些程式碼一路跑到天荒地老。

所以,嗯,JavaScript 的怪異特性大概會永遠留在那裡! 😂

在經過相當長一段休息之後,我決定把我曾經很受歡迎的 JS WTF 系列重新帶回來。理由非常簡單。我正從 iJS Munich 回程,而且我在法蘭克福機場還有兩個小時要消磨……(別問)。時間不夠寫一篇劃時代、深度技術性的文章,但又絕對多到不能只是坐在這裡盯著牆壁發呆。

那就來看看我最喜歡的 JavaScript 結構之一:

{} + [] // 0

沒錯,你沒看錯。在適當的情況下,如果我們在 JavaScript 中把一個空物件加上空陣列,結果會得到 0。而且這完全合理! 🤡

等等,什麼?為什麼會這樣?

你首先需要知道的是,JavaScript 在這種情況下,不一定會把 {} 解讀成物件! 根據上下文不同,JavaScript 解析器可以把這個表達式視為兩個完全不同的東西。

看看這段程式碼,當它出現在傳統 JavaScript 腳本中、一個敘述的開頭時:

{} + []

JavaScript 可能會這樣解讀:

{}    // 一個空的程式區塊
+[]   // 對空陣列套用一元加號(會出什麼事呢?)

沒錯。第一個 {} 根本不是物件字面值,只是一個什麼都不做的空程式區塊。而第二部分呢?嗯,精彩的地方就從這裡開始。

型別轉換的魔法 ✨

這個表達式:

+[]

是 型別轉換 的經典例子之一,而這也是我們這門美妙語言最有名的特性之一。

你大概知道,JavaScript 是一種動態型別且弱型別的語言。

動態型別表示變數不會永久綁定在某一種型別上。你可以先把數字指派給變數,之後再改成字串,而 JavaScript 不會抱怨。

但還不只這樣!JavaScript 也會進行隱式型別轉換,這也是它常被稱為弱型別的原因之一。而有時候,這些轉換會產生……相當有創意的結果。

以一元加號運算子為例:

+[]

不同於二元 + 運算子,它可以做數字加法或字串串接,一元加號會嘗試把它的運算元轉換成數值。 在這個例子裡,它本質上就像:

Number([])

沒錯,我們要把陣列轉成數字。因為不然呢?

但在 JavaScript 完成這個操作之前,還需要先做另一個轉換。陣列是一種物件,而 JavaScript 必須先從它取得一個原始值。對於一般的空陣列來說,這個過程最後會呼叫它的 toString() 方法。

你猜怎麼著?

[].toString(); // ""

空陣列會變成空字串。

所以我們的轉換實際上會像這樣:

Number([]);

// 首先,陣列會變成原始字串:
Number("")]

// 然後,空字串會變成:
0

因為:

Number("") // 0

所以:

+[] // 0

這就表示我們原本的結構:

{} + []

當它被解析成「一個空區塊後面接著一元加號」時,實際上就會變成:

{} // 什麼都不做

+[] // 0

這就是我們得到 0 的原因! 簡單又明顯,對吧?🤡

公平地說,我想不到還有哪種語言會讓這種結構剛好產生一個看起來這麼正常的結果。就連我們摯愛的 PHP,我相信遇到這種情況大概也會直接拋出致命錯誤。不過 PHP 也有自己一籮筐令人驚奇的狀況,所以也許我們不該太嚴苛。

有一個很重要的細節:這個行為取決於程式碼是如何被解析的。 例如:

// 在傳統腳本中,作為獨立敘述:
{} + [] // 0

// 強制 JavaScript 把 {} 解讀成物件:
({} + []) // "[object Object]"

// 在函式呼叫中:
console.log({} + []); // "[object Object]"

好吧,那 [] + [] 呢?

既然你已經了解型別轉換,這題應該就很直觀了:

[] + [] // ""

這裡使用的是二元 + 運算子。JavaScript 會把兩個陣列都轉成原始值。嗯,兩個空字串相加,你還能期待什麼呢?😀 什麼都加什麼都等於什麼都沒有!太美了。

然後,照這個說法,加法甚至不是交換律了 😂

沿用同樣的邏輯,我們再試一個表達式:

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

等等。

我們不是才剛確認過:

{} + [] // 0

那為什麼把運算元對調之後,結果會完全不同?看來在 JavaScript 裡,加法的交換律也是可選的。xD

嚴格來說,這裡不一定總是在做數學加法,而且解析器對這些表達式的處理方式也不同。

不過,別讓技術細節破壞了這個好笑的梗。實際上發生的是這樣。這個表達式中:

[] + {}

空陣列會變成空字串:

[].toString() // ""

而空物件呢?一般的 JavaScript 物件有它自己很棒的字串表示法:

({}).toString() // "[object Object]"

所以 JavaScript 實際上會進行:

"" + "[object Object]"

結果得到:

"[object Object]"

就這樣!簡單得像搭建一把連枷。 好吧,這是波蘭諺語 proste jak budowa cepa 的直譯,意思是某件事簡單到荒謬、非常容易理解。我覺得這根本超適合 JavaScript。😂

最後的想法

希望你喜歡這趟深入 JavaScript 怪奇世界的小旅行。也希望讀完這篇文章之後,你會更加欣賞這門偉大語言的美感與原創性。

畢竟,JavaScript 已經娛樂我們超過 30 年了,而我強烈懷疑它還藏著滿滿的驚喜。

最棒的是?上面所有例子都不是 bug。 它們全都是 JavaScript 解析規則、型別轉換,以及語言長年向下相容歷史所造成的結果。所以,下次如果有人說 JavaScript 壞掉了,因為 {} + [] 竟然可以等於 0,你就可以自豪地解釋為什麼這件事其實完全合理。

下次我會告訴你為什麼 {} + {} = NaN……但可惜時光飛逝,當你玩得正開心時尤其如此,我很快就得準備搭機了!


P.S. 如果你喜歡我的內容,歡迎跟我聯繫,或到 LinkedIn 按個讚!❤️


原文出處:https://dev.to/sylwia-lask/yes-0-no-javascript-isnt-broken-14ob


精選技術文章翻譯,幫助開發者持續吸收新知。

共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。
🏆 本月排行榜
🥇
站長阿川
📝7   💬1  
273
🥈
boluoji
📝5   ❤️3
97
🥉
mike
1
評分標準:發文×10 + 留言×3 + 獲讚×5 + 點讚×1 + 瀏覽數÷10
本數據每小時更新一次
📢 贊助商廣告 · 我要刊登