玩轉JS的類型轉換黑科技

原文來源於個人github

0.前言

js身爲一種弱類型的語言,不用像c語言那樣要定義int、float、double、string等等數據類型,由於容許變量類型的隱式轉換和容許強制類型轉換。咱們在定義一個變量的時候,就一個var、let、const搞定,不用擔憂數據的類型。好比常見的字符串拼接,用+號能夠實現變量和字符串的拼接。 總的來講,通常的規則是javascript

  • !後面的字符會被轉爲換布爾
  • +後面的字符會被轉換爲數值(-也是差很少)
  • []+後面的字符會被轉換爲字符串 對於object和number、string、boolean之間的轉換關係,這裏偷網上一幅圖

1

  • Object 與Primitive,須要Object轉爲Primitive
  • String 與 Boolean,須要兩個操做數同時轉爲Number。
  • String/Boolean 與 Number,須要String/Boolean轉爲Number。
  • undefined 與 null ,和全部其餘值比較的結果都是false,他們之間==成立

ToPrimitive是指轉換爲js內部的原始值,若是是非原始值則轉爲原始值,調用valueOf()和obj.toString()來實現。valueOf返回對象的值:在控制檯,當你定義一個對象按回車,控制檯打印的是Object{...},obj.toString()返回對象轉字符串的形式,打印的是"[object Object]"前端

  • 若是參數是Date對象的實例,那麼先toString()若是是原始值則返回,不然再valueOf(),若是是原始值則返回,不然報錯。
  • 若是參數不是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.從[]==![]開始

你們也可能據說過[]!=[],主要是由於他們是引用類型,內存地址不一樣因此不相等。那麼爲何加了一個!就能等於了?不是內存地址仍是不同嗎? 這又引出一個問題,符號的優先度vue

1 . [] ()
2 ++ — ~ !
3 * / %
4 + - +
5 << >>
4 + - +
5 < <= > >=
4 + - +
6 == != === !==

能夠看見,!優先度是第二,因此先判斷!再判斷= 給[]取反,會是布爾值,[]的取反的布爾值就是falsejava

2.1 []的反就是false?

ECMA規範:

非布爾類型轉布爾類型:undefined、null 、0、±0、NaN、0長度的字符串=》false,對象=》true 非數字類型轉數字類型:undefined=》NaN,null=》0,true=》1,false=》0,字符串:字符串數字直接轉數字類型、字符串非數字=》NaNgit

[]也是對象類型(typeof [] == "object"),轉爲布爾類型的![]就是falsegithub

2.2 等號兩邊對比

咱們知道,在比較類型的時候,先會進行各類各樣的類型轉換。 從開頭的表格能夠看見,他們比較的時候都是先轉換爲數字類型。右邊是布爾值false,左邊爲一個空數組對象,對於左邊,先進行P操做(ToPrimitive([])),先執行valueOf([])返回的是[],非原始類型,再 [].toString(),返回的是"",那P操做以後,結果就是""了 最後,左邊""和右邊false對比,他們再轉換爲數字,就是0==0的問題了數組

3.從已有的獲得想不到的

3.1 間接獲取數組方法

咱們知道,數組有本身的一套方法,好比var arr = [1,2];arr.push(1),咱們能夠寫成[1,2].push(1),還能夠寫成[1,2]['push'](1),那麼前面拋出的問題就解決了ui

[]['push'](1) //[1]
[]["map"] //function map() { [native code] }
[]["map"]+[] // "function map() { [native code] }"
複製代碼

3.2 間接進行下標操做

3.2.1數字的獲取

咱們能夠經過類型轉換,得到0和1兩個數字,既然能獲得這兩個數字,那麼也能夠獲得其餘的一切數字了: +[] === 0; +!![] === 1 那麼, +!![]+!![] ===2,+((+![])+(+!![])+[]+(+![]))===10(注意:中間沒[]的話,就是數字的1+0,結果就是1了,有的話就是'1'+''+'0') +((+![])+(+!![])+[]+(+![]))-!![] ===9 簡直就是無所不能this

3.2.2 字符串下標

(![]+[])[+[]] //"f"
(![]+[])[+!![]] // "a"
複製代碼

(![]+[])是"false",其實(![]+[])[+[]] 就至關於"false"[0],第一個字母,就是f 咱們就能夠從上面的那些得到單詞的字符串得到其中的字母了 好了,說道這裏,要是誰說前端簡單,那就給他一個(![]+[])[+!![]+!![]+!![]] +([]+{})[+!![]+!![]]spa

4.關於(a==1 && a==2 && a==3)

4.1 ==

近來有人問這個問題(a==1 && a==2 && a==3) 或者(a===1 && a===2 && a===3) 能不能爲true? 事實上是能夠的,就是由於在==比較的狀況下,會進行類型的隱式轉換。前面已經說過,若是參數不是Date對象的實例,就會進行類型轉換,先valueOf再obj.toString() 因此,咱們只要改變原生的valueOf或者tostring方法就能夠達到效果:

var a = {
  num: 0,
  valueOf: function() {
    return this.num += 1
  }
};
var eq = (a==1 && a==2 && a==3);
console.log(eq);

//或者改寫他的tostring方法 
var num = 0;
Function.prototype.toString = function(){
	return ++num;
}
function a(){}

//還能夠改寫ES6的symbol類型的toP的方法
var  a = {[Symbol.toPrimitive]: (function (i) { return function(){return  ++i } }) (0)};
複製代碼

每一次進行等號的比較,就會調用一次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源碼實現雙向數據綁定中,就利用了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了

相關文章
相關標籤/搜索