[] == ![]發生了什麼?

記不清在某處看見了這一比較,當時對強制轉換這塊理解的尚未特別清晰,故有此一文。覺得我會以標題的表達式來展開?那你就錯了,下面直接上[] == ![]是如何轉換的:javascript

  1. 由於!運算符的優先級比較高,因此表達式右側先運行![],得出false,表達式變爲[] == false
  2. 強制將false轉換爲0,表達式變爲[] == 0
  3. 將[]強制轉換爲原始類型後爲"",表達式變爲"" == 0
  4. 將""轉換爲Number類型,表達式變爲0 == 0
  5. 兩側類型相同,直接返回0 === 0的結果true

前言

本文旨在總結js中強制轉換的規則及觸發強制轉換的幾種場景。ES6標準中定義了六種原始類型,分別是Undefined,Null,String,Number,Boolean,Symbol。本文中的強制轉換指的是在代碼運行時,觸發了數值的隱式轉換,而不是代碼顯示的指定轉換操做。java

原始類型間強制轉換

發生在原始類型之間的轉換,以我的的理解是其餘類型轉換爲String,Number或者Boolean類型。算法

轉換爲String類型

其餘原始類型轉換爲String類型一般發生在+兩邊存在字符串時,會將+另外一邊的值轉換爲String類型。
考慮以下代碼:函數

var strAddNum = "test" + 1;
var numAddStr = 1 + "test";
var boolAddStr = true + "test";
var undAddStr = undefined + "";
var nullAddStr = null + "";
console.log(strAddNum);
console.log(numAddStr);
console.log(boolAddStr);
console.log(undAddStr);
console.log(nullAddStr);

代碼傳送門,以上代碼的運行結果均爲字符串。其餘原始類型轉換爲String類型基本是其值的字符串形式,具體以下:編碼

  • Undefined,"undefined"
  • Null,"null"
  • Boolean,"true"或"false"
  • Number,值爲NaN,"NaN"
  • Number,值爲+0或-0,"0"
  • Number,值爲+Infinity,"Infinity"
  • Number,值爲-Infinity,"-Infinity"

Number轉爲字符串具體可參考ES2018 7.1.12.1章節code

注意:Symbol類型沒法轉換爲String類型。對象

轉換爲Number類型

轉換爲Number類型的狀況,+-*/%等運算中,除了+以外其餘運算均會轉換成Number類型,+運算時須要知足兩側未出現String類型,該值纔會被轉換爲Number類型。+運算時狀況較爲複雜,後面會專門描述其相關轉換規則。考慮以下代碼:ip

var trueAddTrue = true + true;
var trueAddFalse = true + false;
var trueAdda0 = true + 0;
var nullAddTrue = null + true;
var undefinedAdd0 = undefined + 0;
var strAdd0 = "" + 0;
console.log(trueAddTrue);
console.log(trueAddFalse);
console.log(trueAdda0);
console.log(nullAddTrue);
console.log(undefinedAdd0);
console.log(strAdd0);

代碼傳送門,在運行代碼以前能夠先考慮下以上代碼答打印的結果分別是什麼?而後再運行,看是否符合你的預期。其餘原始類型轉換爲Number類型的具體以下:開發

  • Undefined,NaN
  • Null, +0
  • Boolaen,值爲true,1
  • Boolean,值爲false,+0
  • String,不可轉爲Number的,NaN
  • String,可轉爲Number的就是其對應的Number值(具體可參考ES2018 7.1.3.1

注意:Symbol類型一樣沒法轉換爲Number類型。字符串

轉換爲Boolean類型

轉換爲Boolean類型的狀況較爲簡單,除了如下狀況轉換爲Boolean類型會是false,其餘狀況均是true

  • Undefined
  • Null
  • Number,+0,-0,NaN
  • String,長度爲0的字符串

這幾種false的狀況在ES標準中有明確規定7.1.2

對象強制轉換爲原始類型

ES中將對象轉換爲原始類型的算法,大體可描述爲三種情形:

  1. 若是該對象設置了[Symbol.toPrimitive],調用該函數,若是其返回值爲非Object類型則返回結果,不然拋出TypeError異常
  2. 若未指定轉換提示則轉換提示爲"default"
  3. 若轉換提示爲"default",則將其置爲"number"
  4. 當指定轉換提示爲"number"時先調用該對象的valueOf函數並判斷其結果,若是是原始類型則返回結果,不然調用該對象的toString函數並判斷其返回結果,若是結果爲原始類型則返回,不然拋出異常TypeError
  5. 當指定轉換提示爲"string"時先調用toString函數並判斷其返回結果,若是是原始類型則返回結果,不然調用該對象的valueOf函數並判斷其返回結果,若是結果爲原始類型則返回,不然拋出異常TypeError

上述三種情形中第一種情形優先級最高,第二三種情形優先級並列,具體須要根據使用場景判斷是哪種。其中的指定轉換提示是ES標準內部調用該算法時指定的。

第一種情形只有Symbol對象和Date對象內置了[Symbol.toPrimitive],且該屬性的writeable爲false,enumerable爲false,configurable爲true
對象轉換爲原始類型時發生的強制轉換非特殊狀況均爲第二種,第三種狀況較爲少見。在正常編碼工做中應該使用第二種情形就夠用了,第三種情形幾乎不會出現,要了解更多細節可查閱ES標準。

var test = {
  [Symbol.toPrimitive]: function(hint) {
     console.log(hint)
  },
  valueOf: function() {
      console.log("valueOf")
  },
  toString: function() {
      console.log("toString")
  }
}
test + "";  //"default"
test * 0;   //"number"
String(test);   //"string"

代碼傳送門上述代碼指定了分別指定了test對象的[Symbol.toPrimitive],valueOf和toString函數,能夠觀察到並valueoOf和toString函數均未被調用,指定的[Symbol.toPrimitive]函數能夠接受一個提示參數,這個參數就是強制轉換時的強制轉換提示。這樣咱們在函數中就能夠根據轉換場景的不一樣分別返回不一樣的值。

原始類型強制轉換爲對象(裝箱)

在開始描述這個問題以前,能夠先思考一下,都有哪些場景會是強制的將原始類型轉換爲對象,其實這種場景幾乎在js代碼中隨處可見,考慮以下代碼:

var str = "testString";
str.replace("test", "");

如上代碼中定義的str的值並非一個對象而是一個原始類型String,原始類型顯然是沒有方法能夠調用的。

實際上這裏的str在執行str.replace時str其值會被強制轉換爲對象,獲得一個String類型的實例對象,而該實例的原型上定義了一系列方法,且該實例是沒法被獲取的,在執行完這行代碼後,該實例就會被回收,因此這裏的str依然是一個字符串。

考慮以下代碼:

var a = 3;
a.fn = function(){};
a.fn();

強制轉換的幾種場景

在js代碼中會出現強制轉換的場景一般有三種:

  • +運算
  • -,*,/,%運算
  • ==比較
  • 做爲判斷條件

+運算

一元+運算

作一元+運算時,均會被強制轉爲Number類型,例如

var a = {
    [Symbol.toPrimitive]: function(hint) {
        console.log(hint);  // number
        if(hint === "number") {
            return 2;
        } else {
            return 9;
        }
    }
};
console.log(+a);   // 2

var b = "3";
console.log(+b);    // 3

代碼傳送門

二元+運算

二元+運算爲幾種強制轉換中複雜度僅次於==比較的一種情形,我的總結其轉換步驟以下:

  1. 先將兩側數值強制轉換爲原始類型(未指定轉換提示,即轉換提示爲hint default);
  2. 若兩側存在String類型,均轉換爲String類型,則返回兩側拼接的字符串;
  3. 若第2未返回,則兩側數值強制轉換爲Number類型,返回計算結果;
var a = "";
var b = {
    [Symbol.toPrimitive]: function(hint) {
        console.log(hint);  // "default"
        if(hint === "default") {
            return 2;
        } else {
            return 9;
        }
    }
};
var c = a + b;  //這裏b轉換爲原始類型返回的是Number類型2,因爲a是"",因此b被轉換爲"2",後與""拼接返回"2"
console.log(c); // "2"

var d = 3;
var e = {
    [Symbol.toPrimitive]: function(hint) {
        console.log(hint);  // "default"
        if(hint === "default") {
            return 2;
        } else {
            return 9;
        }
    }
};
var f = d + e;  //這裏e轉換爲原始類型返回的是Number類型2,因爲兩側均沒有String類型,則至第3步,強制轉換爲Number後返回兩側相加的結果5
console.log(f); // 5

代碼傳送門

-,*,/,%運算

這幾個運算符這涉及的強制轉換都是轉換爲Number類型的,因此這裏只要搞清楚轉換爲Number是怎樣的過程就能夠了。上文中已經對原始類型轉換爲Number類型作了描述,這裏補充一下Object轉換爲Number的過程:

  1. 將對象轉換爲原始類型,且轉換時會指定轉換提示爲"number";
  2. 轉換爲原始類型後再根據原始類型轉換爲Number類型進行轉換;
var a = 8;
var b = {
    [Symbol.toPrimitive]: function(hint) {
        console.log(hint);  // "number"
        if(hint === "number") {
            return 2;
        } else {
            return 9;
        }
    }
};
console.log(a-b);   //  6
console.log(a/b);   // 4
console.log(a*b);   // 16
console.log(a%b);   // 0

console.log(undefined * 0);   //NaN
console.log(null * -1); // 0
console.log(false * -1);    //0 
console.log(true * -1); // -1
console.log("1" * -1);  // -1

代碼傳送門

==比較

==比較的基礎===比較

x === y,其具體比較步驟以下:

  1. 若x和y的類型不一致,返回false;
  2. 若x和y爲Number類型,則若x和y中有一個爲NaN返回false;若x和y的值相等則返回true;若x是+0,y是-0或者x是-0,y是+0則返回true;其餘狀況返回false;
  3. 若x和y爲Undefined類型,返回true
  4. 若x和y爲Null類型,返回true
  5. 若x和y爲String類型,其值相同則返回true,不然返回false
  6. 若x和y爲Boolean類型,其值均爲true或均爲false返回true,不然返回false
  7. 若x和y爲Symbol類型,其值爲同一個Symbol值則返回true,不然返回false
  8. 若x和y爲Object類型,其值爲同一個對象(其引用地址相同)則返回true,不然返回false

x == y規則

==比較的轉換規則雖然稍微多一點,實際上也就幾條規則,兩側的數值類型符合哪一種就按哪一種去轉換,只不過有的可能須要轉兩次,具體以下:

  1. 若是兩側類型相等則直接返回===的結果;
  2. 若x爲undefined和y爲null或x爲null和y爲undefined,返回true
  3. 若兩側爲String類型和Number類型,將String類型轉換爲Number類型,繼續用==比較
  4. 如有一側存在Boolean類型,將Boolean類型轉換爲Number類型,繼續用==比較
  5. 若兩側爲String,Number或Symbol類型和Object類型,將Object類型轉換原始類型,繼續用==比較
  6. 其餘返回false

下面列舉一些可能有點違反直覺的比較

"0" == false; // true
false == 0; // true
false == ""; // true
false == []; // true
"" == 0; // true
"" == []; // true
0 == []; // true
[] == ![];  //true

做爲條件判斷

這種情形到沒有太多可說的,基本上就是,除了undefined,null,+0,-0,NaN,""這六個值會被轉爲false,其餘狀況均爲true;

出現將不是Boolean類型的值強制轉換的狀況爲

  1. if(...)
  2. for(...;...;...)第二個條件表達式
  3. while(...)和do...while(...)中的條件表達式
  4. ...?...:...三元表達式中的第一個條件表達式
  5. ||和&&

結論

其實上面描述了這麼多,平常開發環境中用到比較多的應該是做爲判斷條件,+,==這三種狀況了,這三種中最多見的應該是判斷條件的狀況了,這種狀況反而是最簡單的一種了。

相關文章
相關標籤/搜索