解釋:為什麼 ++[[]][+[]]+[+[]]
= 10
[0]
是一個帶有0成員的陣列,[0][0]
是取它的第1個成員,所以必是0
。
用了[0][0] = '1'
雖然改了第1成員的值,但下一個[0][0]
是獨立的取成員值的表示式,所以得到0數字值。
[] = 1
是右值不是iterable(可迭代的)造成的錯誤,這應該是”解構賦值”造成的錯誤,以不同的瀏覽器除錯:
1 2 3 4 5 6 7 8 |
// Chrome TypeError: undefined is not a function // Firefox TypeError: 1 is not iterable // Safari TypeError: [] is not a function. (In '[]', '[]' is undefined) |
陣列解構賦值的話,右值必需是iterable(可迭代的),下面的例子的錯誤與[] = 1
是一樣錯誤,所以應該會先檢查右值是否為iterable時,先丟擲型別錯誤:
1 2 3 |
[] = {}; [] = undefined; [] = null; |
最後的,[] = '1'
不會有錯誤,是因為字串是屬於iterable(可迭代的)。
解構賦值可以參考這篇文章。
JS的{} + {}與{} + []
的結果是什麼?
ToPrimitive內部運算
因此,加號運算子只能使用於原始資料型別,那麼對於物件型別的值,要如何轉換為原始資料型別?下面說明是如何轉換為原始資料型別的。
在ECMAScript 6th Edition #7.1.1,有一個抽象的ToPrimitive運算,它會用於物件轉換為原始資料型別,這個運算不只會用在加號運算子,也會用在關係比較或值相等比較的運算中。下面有關於ToPrimitive的說明語法:
ToPrimitive(input, PreferredType?)input代表代入的值,而PreferredType可以是數字(Number)或字串(String)其中一種,這會代表”優先的”、”首選的”的要進行轉換到哪一種原始型別,轉換的步驟會依這裡的值而有所不同。但如果沒有提供這個值也就是預設情況,則會設定轉換的hint值為”default”。這個首選的轉換原始型別的指示(hint值),是在作內部轉換時由JS視情況自動加上的,一般情況就是預設值。
而在JS的Object原型的設計中,都一定會有兩個valueOf
與toString
方法,所以這兩個方法在所有物件裡面都會有,不過它們在轉換e有可能會交換被呼叫的順序。
當PreferredType為數字(Number)時
當PreferredType為數字(Number)時,input為要被轉換的值,以下是轉換這個input值的步驟:
- 如果input是原始資料型別,則直接返回input。
- 否則,如果input是個物件時,則呼叫物件的valueOf()方法,如果能得到原始資料型別的值,則返回這個值。
- 否則,如果input是個物件時,呼叫物件的toString()方法,如果能得到原始資料型別的值,則返回這個值。
- 否則,丟擲TypeError錯誤。
當PreferredType為字串(String)時
上面的步驟2與3對調.
PreferredType沒提供時,也就是hint為”default”時
與PreferredType為數字(Number)時的步驟相同。
數字其實是預設的首選型別,也就是說在一般情況下,加號運算中的物件要作轉型時,都是先呼叫valueOf再呼叫toString。
但這有兩個異常,一個是Date物件,另一是Symbol物件,它們覆蓋了原來的PreferredType行為,Date物件的預設首選型別是字串(String)
。
因此你會看到在一些教程檔案上會區分為兩大類物件,一類是 Date 物件,另一類叫 非Date(non-date) 物件。因為這兩大類的物件在進行轉換為原始資料型別時,首選型別恰好相反。
模擬程式碼說明
1 2 3 4 5 6 7 8 |
a + b: pa = ToPrimitive(a) pb = ToPrimitive(b) if(pa is string || pb is string) return concat(ToString(pa), ToString(pb)) else return add(ToNumber(pa), ToNumber(pb)) |
JS對於Object與Array的設計
在JS中所設計的Object純物件型別的valueOf與toString方法,它們的返回如下:
valueOf方法返回值: 物件本身。(所以ToPrimitive最後要返回toString的值了
)
toString方法返回值: “[object Object]”字串值,不同的內建物件的返回值是”[object type]”字串,”type”指的是物件本身的型別識別,例如Math物件是返回”[object Math]”字串。但有些內建物件因為覆蓋了這個方法,所以直接呼叫時不是這種值。(注意: 這個返回字串的前面的”object”開頭英文是小寫,後面開頭英文是大寫)
一元正號(+),
具有讓首選型別(也就是hint)設定為數字(Number)的功能,所以可以強制讓物件轉為數字型別,一般的物件會轉為:
這裡首選型別其實本身就是數字,+讓toString輸出的字串再強轉了一次。
1 2 |
> +{} //相當於 +"[object Object]" NaN |
當然,物件的這兩個方法都可以被覆蓋,你可以用下面的程式碼來觀察這兩個方法的執行順序,下面這個都是先呼叫valueOf的情況:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
let obj = { valueOf: function () { console.log('valueOf'); return {}; // object }, toString: function () { console.log('toString'); return 'obj'; // string } } console.log(1 + obj); //valueOf -> toString -> '1obj' console.log(+obj); //valueOf -> toString -> NaN console.log('' + obj); //valueOf -> toString -> 'obj' |
例項
基本型別間運算
- 字串 + 其他原始型別字串在加號運算有最高的優先運算
1234567891011> '1' + 123"1123"> '1' + false"1false"> '1' + null"1null"> '1' + undefined"1undefined"
- 數字 + 其他的非字串的原始資料型別數字為優先
12345678> 1 + true //true轉為1, false轉為02> 1 + null //null轉為01> 1 + undefined //null轉為NaNNaN
- 數字/字串以外的原始資料型別作加法運算就是轉為數字再運算
12345678> true + true2> true + null1> undefined + nullNaN
物件型別間運算
- 空陣列 + 空陣列
12> [] + []""
兩個陣列相加,依然按照valueOf -> toString的順序,但因為valueOf是陣列本身,所以會以toString的返回值才是原始資料型別,也就是空字串,所以這個運算相當於兩個空字串在相加,依照加法運算規則第2步驟,是字串連線運算(concatenation),兩個空字串連線最後得出一個空字串。
- 空物件 + 空物件
特別注意: {} + {}在不同的瀏覽器有不同結果
如果在第一個(前面)的空物件加上圓括號(()),這樣JS就會認為前面是個物件,就可以得出同樣的結果:
1 2 |
> ({}) + {} "[object Object][object Object]" |
注: 上面說的行為這與加號運算的第一個(前面)的物件字面值是不是個空物件無關,就算是裡面有值的物件字面,例如
{a:1, b:2},也是同樣的結果
。
- Date物件
12> 1 + (new Date())> "1Sun Nov 27 2016 01:09:03 GMT+0800 (CST)"
要得出Date物件中的valueOf返回值,需要使用一元加號(+),來強制轉換它為數字型別,例如以下的程式碼:
1 2 |
> +new Date() 1480180751492 |
總結
解構賦值產生的問題
1 2 3 4 |
> {name: 1}['name'] = '2' {name: 1}['name'] = '2' ^^^^^^ SyntaxError: Invalid destructuring assignment target |
上述錯誤。
1 2 |
> {name: 1}[name] = '2' '2' |
{name: 1}[name]相當於{name: 1};[name]。解構賦值成功。
{}問題
1 2 3 4 5 |
> var name = 'test' > {[name]:1} Object {1: 1} > {[name]:1};[name] = '1' VM174:1 Uncaught SyntaxError: Unexpected token : |
上述錯誤其實是由於,{[name]:1}中{}是表示式,返回物件;{[name]:1};[name] = ‘1’中{}是語句,語句中不允許”[name]:1“,換而言之語句中允許”{name: 1}”寫法。
{} + {}
{} + {}的結果是會因瀏覽器而有不同結果,Chrome(v55)中是object Object字串連線,但其它的瀏覽器則是認為相當於+{}運算,得出NaN數字型別。
{} + []的結果是相當於+[],結果是0數字型別。
Date物件
Date物件上面有提及是首選型別為”字串”的一種異常的物件,這與其他的物件的行為不同(一般物件會先呼叫valueOf再呼叫toString),在進行加號運算時時,它會優先使用toString來進行轉換,最後必定是字串連線運算(concatenation)
1 2 |
> 1 + (new Date()) > "1Sun Nov 27 2016 01:09:03 GMT+0800 (CST)" |
toString()
Object.prototype.toString()才是用來檢測變數本身的型別,typeof是檢測基本型別,instanceof是檢測是否在原型鏈上。(注意一下Object.prototype.toString與Number.prototype.toString、Array.prototype.toString不同)
1 2 3 4 5 6 7 8 9 10 11 12 |
> var a = 1 undefined > a.toString() '1' > Number.prototype.toString.call(a) '1' > Object.prototype.toString.call([1, 2]) '[object Array]' > Array.prototype.toString.call([1, 2]) '1,2' > [1, 2].join() '1,2' |
toString方法返回值: “[object Object]”字串值,不同的內建物件的返回值是”[object type]”字串,”type”指的是物件本身的型別識別,例如Math物件是返回”[object Math]”字串。但有些內建物件因為覆蓋了這個方法,所以直接呼叫時不是這種值。(注意: 這個返回字串的前面的”object”開頭英文是小寫,後面開頭英文是大寫。
1 2 3 4 5 6 |
> Object.prototype.toString.call(null) '[object Null]' > typeof null 'object' > Object.prototype.toString.call(1) '[object Number]' |
Number()、String()與Boolean()
常被搞混的是直接使用Number()、String()與Boolean()三個強制轉換函式的用法,這與包裝物件的用法不同,包裝物件是必須使用new關鍵字進行物件例項化的,例如new Number(123),而Number(‘123’)則是強制轉換其他型別為數字型別的函式。