js類型轉換的各種玩法

前言

對於object和number、string、boolean之間的轉換關係

  • [ ] Object 與Primitive,需要Object轉爲Primitive
  • [ ] String 與 Boolean,需要兩個操作數同時轉爲Number。
  • [ ] String/Boolean 與 Number,需要String/Boolean轉爲Number。
  • [ ] undefined 與 null ,和所有其他值比較的結果都是false,他們之間==成立

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

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

我們知道,[]!=[],主要是因爲他們是引用類型,內存地址不同所以不相等。那麼爲什麼加了一個!就能劃上等於號了

符號的優先度
可以參考mdn上的這個彙總表格: https://developer.mozilla.org... 可以看見,[]==![]這個情況下先判斷!再判斷= 給[]取反,會是布爾值,[]的取反的布爾值就是false

2.1 []的反就是false?

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

回到[]==![]的問題上,[]也是對象類型(typeof [] == "object"),轉爲布爾類型的![]就是false

2.2 等號兩邊對比

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

3.更多玩法

3.1 間接獲取數組方法

我們知道,數組有自己的一套方法,比如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數字的獲取

我們可以通過類型轉換,獲得0和1兩個數字,既然能得到這兩個數字,那麼也可以得到其他的一切數字了: +[] === 0; +!![] === 1 那麼,+!![]+!![] ===2,+((+![])+(+!![])+[]+(+![]))===10

那麼10-1=9也就來了: +((+![])+(+!![])+[]+(+![]))-!![] ===9 簡直就是無所不能

3.2.2 字符串下標

(![]+[])[+[]] //"f"
(![]+[])[+!![]] // "a"

(![]+[])是"false",其實(![]+[])[+[]] 就相當於"false"[0],第一個字母,就是f 我們就可以從上面的那些獲得單詞的字符串獲得其中的字母了,比如:(![]+[])[+!![]+!![]+!![]] +([]+{})[+!![]+!![]]

掌握基本套路後,我們可以隨心所欲發揮,在瀏覽器的控制檯輸入一些符號的組合,然後回車看一下我們寫的“密碼”會轉換成什麼

([][[]] + [])[(+!![] + [] + [] + +![] ) >> +!![]] +
$$('*')[~~[]].nodeName[- ~ -+~[]] +
(this + [])[+!![]+[] + +[] - !!{} - !!{}] +
([]+![])[+!!{} << +!![] -~[]] +
({}+{})[+!![] -~[]]

4. 兩個面試題

曾經遇到兩個這種類型的面試題:

4.1 (a==1 && a==2 && a==3) 能不能爲true

(a==1 && a==2 && a==3)或者(a===1 && a===2 && a===3) 能不能爲true? 事實上是可以的,就是因爲在==比較的情況下,會進行類型的隱式轉換。前面已經說過,如果參數不是Date對象的實例,就會進行類型轉換,先valueOfobj.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 a = {
  num: 4,
  valueOf: function() {
    return this.num -= 1
  }
};
var res = (a==3 && a==2 && a==1);
console.log(res);

另外,如果沒有類型轉換,是 === 的比較,還是可以的。 在vue源碼實現雙向數據綁定中,就利用了defineProperty方法進行觀察數據被改變的時候,觸發set。 每一次訪問對象中的某一個屬性的時候,就會調用這個方法定義的對象裏面的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了

4.2 完善Cash打印三個101

要求只能在class裏面增加代碼:

class Cash {}
const a = new Cash(1)
const b = new Cash(100)
console.log(`${a.add(b)},${Cash.add(a,b)},${new Cash(a+b)}`) // 101,101,101

首先,三個輸出結果是以隱式轉換的形式出現的,這是關鍵之處。 a和b都是new出來的對象,由new Cash(a+b)可以看出構造函數傳入的也是兩個Cash的實例對象。那麼new出來的結果肯定不是簡簡單單的一個object,不然就是被轉換成'[object Object]',但是你又不得不以object類型出現,那就只能魔改隱式轉換用到的toString和valueOf

class Cash {
    constructor (a) {
        this.m = a // 緩存真正的值
        this.valueOf = function () {
            console.log('value')
            return a
        }
    }

    add ($) { // a.add(b)
        return this.m + $
    }

    toString () { // 隱式轉換調用
        return this.m
    }

    static add (v1, v2) { //Cash.add
        return v1 + v2
    }
}

最後

然而,實際項目中兩個數據作比較的時候,我們儘量不要寫甚至完全不要寫兩個等號,應該寫三個等號,而且js也慢慢有向強類型過渡的趨勢。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章