面試滑鐵盧
有一天,去面試,遇到這樣的題:
[]+[]
{}+{}
1+[]
複製程式碼
???WTF,誰會沒事這麼寫程式碼,好吧,我錯了,大佬別打我。懵逼之後要幹嘛?當然是要學習一波,於是我滿世界找資料,有好東西當然是要分享一波,好了,接下來我們就一起走進 JavaScript 隱式轉換的世界吧。
加法運算
在 JavaScript 中加法運算規則很簡單,它只做數字和字串的加法操作,所有不是這兩種型別的都會被轉換成這兩種原始資料型別再進行操作。
在 JavaScript 中,資料型別分兩種:
- 原始資料型別(primitives): undefined, null, boolean, number,string,symbol
- 其他的都是物件,包括陣列、函式。
那麼物件是如何轉換成原始資料型別的呢?不要慌,我們繼續看。
ToPrimitive
JS 有一個內部運算 ToPrimitive()
,它用於物件轉換為原始資料型別。
ToPrimitive(input, PreferredType?)
複製程式碼
這個函式接收兩個引數:
- input: 這個引數是輸入的值。
- PreferredType: 這個引數可以Number 或者是 String,這代表了我們的物件優先會轉換成哪種原始資料型別。如果缺少這個引數的話,那對於 Date 的例項,預設為 String,其餘的都為 Number。轉換的步驟也會因為這個引數而有所不同。
下面我們來看一下對於不同的引數,它的轉換過程是什麼樣的?
PreferredType 為 Number
- 如果 input 為原始型別,則直接返回 input。
- 否則,如果 input 是一個物件,則會去呼叫物件的
valueOf()
方法,如果結果為原始型別就直接返回。 - 如果上一步返回的依然是物件,那麼就回去呼叫物件的
toString()
方法,如果結果為原始資料型別就返回。 - 如果還不是原始資料型別就丟擲錯誤,一般是 Uncaught TypeError: Cannot convert object to primitive value。
PreferredType 為 String
引數 String 就不細說了,當引數為 String 的時候,上面的第二步和第三步交換就行了,也就是先呼叫 toString()
再呼叫 valueOf()
。
+ 運算
value1 + value2
複製程式碼
上面的操作方式如下:
- 將兩個運算元轉換成基本資料型別:
// PreferredType被省略,因此非日期為 Number,日期為 String。
prim1 = ToPrimitive(value1)
prim2 = ToPrimitive(value2)
複製程式碼
- 如果 prim1 或 prim2 是一個字串,則將其轉換為字串並返回結果的連線。
- 否則,將 prim1 和 prim2 都轉換為數字並返回結果的總和。
valueOf 和 toString
這兩個都是 Object 的屬性,可以自己定義,現在我們不管,我們去看看下面幾種情況這兩個方法返回的都是什麼。
// 物件
const a1 = {
a: 1
};
console.log(a1.valueOf());
console.log(a1.toString());
// 陣列
const a2 = [1,2,3];
console.log(a2.valueOf());
console.log(a2.toString());
// 方法
const a3 = function() {
const a = 1;
return 1;
};
console.log(a3.valueOf());
console.log(a3.toString());
複製程式碼
將上面的程式碼放到控制檯列印一下就知道:
- 物件: valueOf() 返回物件本身,toString() 返回值為 [object Object]。
- 陣列: valueOf() 返回物件本身,陣列改寫了 toString(),返回值相當於用
join(',')
的返回值,比如[1,2,3].toString()
返回 "1,2,3"。 - 方法: valueOf() 返回方法本身,Function 也改寫了物件的 toString(),它將程式碼轉為字串值然後返回。
舉個例子
好了,根據我們上面說的,那些面試題簡直灑灑水,我們來看。
[]+{}
複製程式碼
我們如何去分析呢?在這裡,我們首先將 [] 和 {} 轉換成原始資料型別,也就是 ToPrimitive([]) 以及 ToPrimitive({}),PreferredType 預設為 Number,很明顯 [].valueOf()
還是一個物件,所以我們繼續,[].toString()
結果為 "",相同的解析過程 {} 轉換成 "[object Object]"。
好了,現在這個式子是 "" + "[object Object]",我們知道 + 運算只要有字串就拼接運算元,所以結果是 "[object Object]"。
但是,在我進行測試的時候,發現了幾個特殊的例子,{}+1
、{}+[]
這兩個例子在控制檯列印出的結果為 1
和 ""
,很奇怪是吧?我搜了搜資料發現,不同瀏覽器對其的解析不同,它會將前面一個 {} 當成程式碼塊,於是上面的式子就變成了 +1
和 +[]
,所以得出了上面的結果。
總結
好了,經過上面的探究,我相信大家不會再被這些問題難住了,但是要記住,{} 在前面的情況下可能會因為瀏覽器的差異會造成不同的結果,當然,如果你這樣將 {} 用 () 包起來就不會有問題了,或者是先宣告在使用。
更多資源
- What is {} + {} in JavaScript? —— 作者有著德國阮一峰的稱號
更多文章盡在 我的部落格倉庫,如果各位讀者覺得有用,歡迎 star,不勝感激。