深刻js系列-類型(隱式強制轉換)

隱式強制轉換

在其可控的狀況下,減小冗餘,讓代碼更簡潔,不少地方都進行了隱式轉換,好比常見的三目表達式、if()、for()、while、邏輯運算符 || &&,適當經過語言機制,抽象和隱藏一些細枝末節,有助於提升代碼可讀性,以三目表示式爲例數組

a? trueAction : falseAction

!!a ? trueAction : falseAction

1.字符串和數字之間的隱式轉換
經過重載,+ 運算符即能用於數字加法,也能用於字符串拼接。JavaScript 怎樣來判斷咱們性能

var a = "42";
var b = "0";
var c = 42;
var d = 0;
a + b; // "420"
c + d; // 42

一般咱們的理解是,兩個操做數中至少其中之一含有字符串,結果就爲字符串,這種解釋對了一半,看下面例子prototype

var a = [1,2];
var b = [3,4];
a + b; // "1,23,4" 這裏如何解釋呢

咱們得看一下ES5規範:3d

根據ES5 規範11.6.1 節,若是某個操做數是字符串或者可以經過如下步驟轉換爲字符串的話,+ 將進行拼接操做。若是其中一個操做數是對象(包括數組),則首先對其調用ToPrimitive 抽象操做(規範9.1 節),該抽象操做再調用[[DefaultValue]](規範8.12.8
節),以數字做爲上下文

簡單來講,若是+其中的一個操做數是字符串(或者經過此規範能獲得字符串),則進行字符串拼接,不然執行數字加法code

1,23,4的過程解析:對象

數組的valueOf() 操做沒法獲得簡單基本類型值,因而它轉而調用toString()。所以上例中的兩個數組變成了"1,2" 和"3,4"。+ 將它們拼接後返回"1,23,4"。

這裏有一個坑常常被說起ip

[]+ {} // "[object Object]"
{} + [] // 0

[] + {}開發

[].toSting()的結果是"" + {} ,這是至關於隱式將{}轉換爲字符串,因爲valueof返回自身,因此轉向了toString方法,而通常對象沒沒有設置toString方法的話,默認是調用Object.prototype.toString,因此最終結果是 "[obejct Obejct]"

{} + []字符串

{} 被認爲是一個空白代碼塊,解析的是 + [],此時 [].valueof返回是數組自己,而不是基本值,轉而調用toString,返回了"",而後使用Number("")轉換成數字,最後結果是0

這裏有一個須要注意的地方 a+""和String(a)區別在於,a+ ""會根據ToPrimitive抽象操做規則,a+ ""會對a調用valueOf方法,而後經過ToString抽象操做將返回值轉換爲字符串,而String(a)則是直接調用ToString博客

var a = {
  valueOf: function() { return 42; },
  toString: function() { return 4; }
};
a + ""; // "42"
String( a ); // "4"

目前ES6對ToPrimitive抽象操做在Symbol上定義了一個名爲Symbol.toPrimitive變量來供咱們改寫ToPrimitive行爲,看下面例子

// hint表示js引擎獲取到的操做偏好
let obj = {
  [Symbol.toPrimitive](hint) {
    if(hint === 'number'){
      console.log('Number場景');
      return 123;
    }
    if(hint === 'string'){
      console.log('String場景');
      return 'str';
    }
    if(hint === 'default'){
      console.log('Default 場景');
      return 'default';
    }
  }
}
console.log(2*obj); // Number場景 246
console.log(3+obj); // String場景 3default
console.log(obj + "");  // Default場景 default
console.log(String(obj)); //String場景 str

目前內置對象對Symbol.ToPrimitive使用,好比Date對象

var a = [1, 2, 3]

a.valueOf = function () {

    return 'hello'

}

a.valueOf() // "hello"

a.toString() // "1,2,3"

'' + a // "hello"

var date = new Date()

date.valueOf() // 1536416960724

date.toString() // "Sat Sep 08 2018 22:29:20 GMT+0800 (中國標準時間)"

'' + date // "Sat Sep 08 2018 22:29:20 GMT+0800 (中國標準時間)"

這裏date.valueOf可以獲取基本值,可是行爲被Symbol.ToPrimitive定義爲優先返回toString

2.布爾值到數字的隱式強制轉換
再將某些複雜的布爾邏輯轉換爲數字加法的時候,隱式強制轉換能派上大用處,固然狀況比較少見,特殊狀況特殊處理
例如咱們須要保證參數中只能有一個爲true,

function onlyOne(a,b,c) {
  return !!((a && !b && !c) ||
  (!a && b && !c) || (!a && !b && c));
}
var a = true;
var b = false;
onlyOne( a, b, b ); // true
onlyOne( b, a, b ); // true
onlyOne( a, b, a ); // false

參數少咱們尚可對全部狀況進行枚舉,可是3個參數就應該開始使得代碼的可讀性降低,多個參數枚舉使得代碼會更加難讀,還更容易出錯

function onlyOne() {
var sum = 0;
for (var i=0; i < arguments.length; i++) {
// 跳過假值,和處理0同樣,可是避免了NaN
  if (arguments[i]) {
      sum += arguments[i];
    }
    }
     return sum == 1;
}
var a = true;
var b = false;
onlyOne( b, a ); // true
onlyOne( b, a, b, b, b ); // true

「異常」的邏輯運算符 && ||,我以爲應該稱之爲選擇器運算符,由於它們是返回操做數之中的其一

var a = 42;
var b = "abc";
var c = null;
a || b; // 42
a && b; // "abc"
c || b; // "abc"
c && b; // null

3.符號的強制轉換
ES6容許符號顯式轉換字符串,而隱式轉換會產生錯誤

var s1 = Symbol( "cool" );
String( s1 ); // "Symbol(cool)"
var s2 = Symbol( "not cool" );
s2 + ""; // TypeError

4.寬鬆相等和嚴格相等

4.1== 、=== 的區別,常見的誤區是「== 檢查值是否相等,=== 檢查值和類型是否相等」。聽起來蠻有道理,然而
還不夠準確。不少JavaScript 的書籍和博客也是這樣來解釋的,可是很遺憾他們都錯了。
正確的解釋是:「== 容許在相等比較中進行強制類型轉換,而=== 不容許。」

4.2相等比較的性能
若是兩個值的類型不一樣,咱們就須要考慮有沒有強制類型轉換的必要,有就用==,沒有就
用===,不用在意性能。

4.3抽象相等,==運算符的行爲,這是隱式強制轉換最讓人詬病的,開發人員認爲它們過於晦澀,很難掌握和運用。

規則

兩個值類型相同,僅比較它們相等,須要注意很是規的NaN!=NaN以及+0 == -0,對於對象,只判斷其引用是否相等,不發生強制類型轉換

兩個不一樣類型的值會發生隱式強制轉換

4.3.1 字符串和數字之間的相等比較,將字符串轉換爲數字再進行比較,具體規範以下

(1) 若是Type(x) 是數字,Type(y) 是字符串,則返回x == ToNumber(y) 的結果。
(2) 若是Type(x) 是字符串,Type(y) 是數字,則返回ToNumber(x) == y 的結果。

例子

var a = 42;
var b = "42";
a === b; // false
a == b; // true

4.3.2 其餘類型和布爾類型之間的相等比較,將布爾值轉換爲數字,而後繼續比較,這種比較會致使寬鬆相等會進行屢次的隱式類型轉換,不要使用 寬鬆相等來和布爾值比較,它的隱式轉換過於晦澀。

(1) 若是Type(x) 是布爾類型,則返回ToNumber(x) == y 的結果;
(2) 若是Type(y) 是布爾類型,則返回x == ToNumber(y) 的結果

例子

var x = true;
var y = "42";
x == y; // false

解析

Type(x) 是布爾值,因此ToNumber(x) 將true 強制類型轉換爲1,變成1 == "42",兩者的類型仍然不一樣,"42" 根據規則被強制類型轉換爲42,最後變成1 == 42,結果爲false。

5 null和undefined之間的相等比較,在==狀況下它們相等
規範

(1) 若是x 爲null,y 爲undefined,則結果爲true。
(2) 若是x 爲undefined,y 爲null,則結果爲true。

6 對象和非對象之間的相等比較,對對象進行ToPrimitive操做而後進行操做
規範

(1) 若是Type(x) 是字符串或數字,Type(y) 是對象,則返回x == ToPrimitive(y) 的結果;
(2) 若是Type(x) 是對象,Type(y) 是字符串或數字,則返回ToPromitive(x) == y 的結果。

例子

var a = 42;
var b = [ 42 ];
a == b; // true

解析

[ 42 ] 首先調用ToPromitive 抽象操做(參見4.2 節),返回"42",變成"42" == 42,而後又變成42 == 42,最後兩者相等。

== 中的假值比較

"0" == null; // false
"0" == undefined; // false
"0" == false; // true -- 暈!
"0" == NaN; // false
"0" == 0; // true
"0" == ""; // false
false == null; // false
false == undefined; // false
false == NaN; // false
false == 0; // true -- 暈!
false == ""; // true -- 暈!
false == []; // true -- 暈!
false == {}; // false
"" == null; // false
"" == undefined; // false
"" == NaN; // false
"" == 0; // true -- 暈!
"" == []; // true -- 暈!
"" == {}; // false
0 == null; // false
0 == undefined; // false
0 == NaN; // false
0 == []; // true -- 暈!
0 == {}; // false

應對假值比較的特殊狀況

"0" == false; // true -- 暈!
false == 0; // true -- 暈!
false == ""; // true -- 暈!
false == []; // true -- 暈!
// 前面四種咱們能夠經過不使用== boolean來避免
// 這個比較少用
"" == []; // true -- 暈!

// == "" == 0 咱們得特別注意,在有 [] 、0 、"" 咱們儘可能少使用
"" == 0; // true -- 暈!
0 == []; // true -- 暈!
小結,對於寬鬆相等,對象會被ToPrimitive抽象操做轉換爲基本值,而後咱們只須要記住數字、布爾、字符串比較的隱式轉換規則便可
字符串和數字比較,字符串會被轉換數字
布爾值和其餘類型比較,會被轉換成數字
相關文章
相關標籤/搜索