本文是lhyt本人原創(chuàng),希望用通俗易懂的方法來理解一些細節(jié)和難點。轉載時請注明出處。文章最早出現(xiàn)于本人github
0.前言
js身為一種弱類型的語言,不用像c語言那樣要定義int、float、double、string等等數(shù)據(jù)類型,因為允許變量類型的隱式轉換和允許強制類型轉換。我們在定義一個變量的時候,就一個var、let、const搞定,不用擔心數(shù)據(jù)的類型。比如常見的字符串拼接,用+號可以實現(xiàn)變量和字符串的拼接。
總的來說,一般的規(guī)則是
!后面的字符會被轉為換布爾
+后面的字符會被轉換為數(shù)值(-也是差不多)
[]+后面的字符會被轉換為字符串
對于object和number、string、boolean之間的轉換關系,這里偷網(wǎng)上一幅圖

Object 與Primitive,需要Object轉為Primitive
String 與 Boolean,需要兩個操作數(shù)同時轉為Number。
String/Boolean 與 Number,需要String/Boolean轉為Number。
undefined 與 null ,和所有其他值比較的結果都是false,他們之間==成立
ToPrimitive是指轉換為js內部的原始值,如果是非原始值則轉為原始值,調用valueOf()和obj.toString()來實現(xiàn)。valueOf返回對象的值:在控制臺,當你定義一個對象按回車,控制臺打印的是Object{...},obj.toString()返回對象轉字符串的形式,打印的是"[object Object]"
如果參數(shù)是Date對象的實例,那么先toString()如果是原始值則返回,否則再valueOf(),如果是原始值則返回,否則報錯。
如果參數(shù)不是Date對象的實例,同理,不過先valueOf再obj.toString()。
1.奇葩例子
![] //false;
+[] // 0
+![] // 0
[]+[] // ""
{}+{}//"[object Object][object Object]"
{}+[]//0
{a:0}+1 // 1
[]+{}//"[object Object]"
[]+![]//"false"
{}+[]//0
![]+[] // "false"
''+{} //"[object Object]"
{}+'' //0
[]["map"]+[] //"function map() { }"
[]["a"]+[] // "undefined"
[][[]] + []// "undefined"
+!![]+[] //"1"
+!![] //1
1-{} //NaN
1-[] //1
true-1 //0
{}-1 //-1
[]==![] //true
2.從[]==![]開始
大家也可能聽說過[]!=[],主要是因為他們是引用類型,內存地址不同所以不相等。那么為什么加了一個!就能等于了?不是內存地址還是不一樣嗎?
這又引出一個問題,符號的優(yōu)先度
1. [] ()
2 ++ — ~ !
3 * / %
4 + - +
5 <<? >>?
6 + - +
7 < <= > >=
8 + - +
9 ==? !=? ===? !==
可以看見,!優(yōu)先度是第二,所以先判斷!再判斷=
給[]取反,會是布爾值,[]的取反的布爾值就是false
2.1 []的反就是false?
ECMA規(guī)范:
非布爾類型轉布爾類型:undefined、null 、0、±0、NaN、0長度的字符串=》false,對象=》true
非數(shù)字類型轉數(shù)字類型:undefined=》NaN,null=》0,true=》0,false=》1,字符串:字符串數(shù)字直接轉數(shù)字類型、字符串非數(shù)字=》NaN
[]也是對象類型(typeof [] == "object"),轉為布爾類型的![]就是false
2.2 等號兩邊對比
我們知道,在比較類型的時候,先會進行各種各樣的類型轉換。
從開頭的表格可以看見,他們比較的時候都是先轉換為數(shù)字類型。右邊是布爾值false,左邊為一個空數(shù)組對象,對于左邊,先進行P操作(ToPrimitive([])),先執(zhí)行valueOf([])返回的是[],非原始類型,再
[].toString(),返回的是"",那P操作之后,結果就是""了
最后,左邊""和右邊f(xié)alse對比,他們再轉換為數(shù)字,就是0==0的問題了
3.從已有的得到想不到的
3.1 間接獲取數(shù)組方法
我們知道,數(shù)組有自己的一套方法,比如var arr = [1,2];arr.push(1),我們可以寫成[1,2].push(1),還可以寫成[1,2]['push'](1),那么前面拋出的問題就解決了
[]['push'](1)//[1]
[]["map"] //function map() { [native code] }
[]["map"]+[] // "function map() { [native code] }"
3.2 間接進行下標操作
3.2.1數(shù)字的獲取
我們可以通過類型轉換,獲得0和1兩個數(shù)字,既然能得到這兩個數(shù)字,那么也可以得到其他的一切數(shù)字了:
+[] === 0; +!![] === 1
那么, +!![]+!![] ===2,+((+![])+(+!![])+[]+(+![]))===10(注意:中間沒[]的話,就是數(shù)字的1+0,結果就是1了,有的話就是'1'+''+'0')
+((+![])+(+!![])+[]+(+![]))-!![] ===9
簡直就是無所不能
3.2.2 字符串下標
(![]+[])[+[]] //"f"
(![]+[])[+!![]] // "a"
(![]+[])是"false",其實(![]+[])[+[]] 就相當于"false"[0],第一個字母,就是f
我們就可以從上面的那些獲得單詞的字符串獲得其中的字母了
好了,說道這里,要是誰說前端簡單,那就給他一個(![]+[])[+!![]+!![]+!![]] +([]+{})[+!![]+!![]]
4.關于(a==1 && a==2 && a==3)
4.1 ==
近來有人問這個問題(a==1 && a==2 && a==3) 或者(a===1 && a===2 && a===3) 能不能為true?
事實上是可以的,就是因為在==比較的情況下,會進行類型的隱式轉換。前面已經(jīng)說過,如果參數(shù)不是Date對象的實例,就會進行類型轉換,先valueOf再obj.toString()
所以,我們只要改變原生的valueOf方法就可以達到效果:
var a = {
num: 0,
valueOf: function() {
return this.num += 1
}
};
var eq = (a==1 && a==2 && a==3);
console.log(eq);
每一次進行等號的比較,就會調用一次valueOf方法,自增1,所以能成立。當然,如果換個位置就不行了,var eq = (a==2 && a==1 && a==3);
另外,減法也是同理:
var a = {
num: 4,
valueOf: function() {
return this.num -= 1
}
};
var eq = (a==3 && a==2 && a==1);
console.log(eq);
4.2 ===
如果沒有類型轉換,===的情況,還是可以的。跑題...
在vue源碼實現(xiàn)雙向數(shù)據(jù)綁定中,就利用了defineProperty方法進行觀察,觀察到視圖層的變化并實時反映到model層。
每一次訪問對象中的某一個屬性的時候,就會調用這個方法定義的對象里面的get方法。每一次改變對象屬性的值,就會訪問set方法
在這里,我們自己定義自己的get方法:
var b = 1
Object.defineProperty(window, 'a', {
get:function() { return b++; }
})
var s = (a===1 && a===2 && a === 3 )
console.log(s)
每一次訪問a屬性,a的屬性值就會+1,當然還是交換位置就不能為TRUE了
原文來自lhyt的github