JavaScript自動型別轉換
我們都知道,JavaScript是弱型別語言,在宣告一個變數時,我們無法明確宣告其型別,變數的型別根據其實際值來決定,而且在執行期間,我們可以隨時改變這個變數的值和型別,另外,變數在執行期間參與運算時,在不同的運算環境中,也會進行相應的自動型別轉換。
自動型別轉換一般是根執行環境和操作符聯絡在一起的,是一種隱式轉換,看似難以捉摸,其實是有一定規律性的,大體可以劃分為:轉換為字串型別、轉換為布林型別、轉換為數字型別。今天我們就介紹一下這幾種轉換機制。
轉換為字串型別
當加號“+”作為二元操作符(binary)並且其中一個運算元為字串型別時,另一個運算元將會被無條件轉為字串型別:
// 基礎型別 var foo = 3 + ''; // "3" var foo = true + ''; // "true" var foo = undefined + ''; // "undefined" var foo = null + ''; // "null"
// 複合型別 var foo = [1, 2, 3] + ''; // "1,2,3" var foo = {} + ''; // "[object Object]" // 重寫valueOf()和toString()var o = { valueOf: function() { return 3; }, toString: function() { return 5; } }; foo = o + ''; // "3" o = { toString: function() { return 5; } }; foo = o + ''; // "5"
物件 | valueOf()返回值 | toString()返回值 |
---|---|---|
Array | 返回陣列物件本身。 | 返回一個字串,該字串由陣列中的每個元素的 toString() 返回值經呼叫 join() 方法連線(由逗號隔開)組成。 |
Boolean | 布林值。 | ‘true’/’false’ |
Date | 儲存的時間是從 1970 年 1 月 1 日午夜開始計的毫秒數 UTC。 | 返回一個美式英語日期格式的字串,如’Mon Sep 28 1998 14:36:22 GMT-0700‘ |
Function | 函式本身。 | 返回一個表示函式原始碼的字串 |
Number | 數字值。 | ‘12’ |
Object | 物件本身。這是預設情況。 | 預設情況下,toString()方法被每個Object物件繼承。如果此方法在自定義物件中未被覆蓋,toString() 返回 “[object type]”,其中type是物件的型別。 |
String | 字串值。 | 字串值。 |
Error | 沒有 valueOf 方法。 | 返回一個指定的錯誤物件(Error object)的字串表示 |
轉為布林型別(to boolean)
數字轉為布林型別(from number)
當數字在邏輯環境中執行時,會自動轉為布林型別。0和NaN會自動轉為false,其餘數字都被認為是true,程式碼如下:
// 0和NaN為false,其餘均為true if (0) { console.log('true'); } else { console.log('false'); // output: false } if (-0) { console.log('true'); } else { console.log('false'); // output: false } if (NaN) { console.log('true'); } else { console.log('false'); // output: false } // 其餘數字均為true if (-3) { console.log('true'); // output: true } else { console.log('false'); } if (3) { console.log('true'); // output: true } else { console.log('false'); }
從上面的程式碼中可以看出,非0負值也會被認為是true,這一點需要注意。
字串轉為布林型別(from string)
和數字類似,當字串在邏輯環境中執行時,也會被轉為布林型別。空字串會被轉為false,其它字串都會轉為true
// 空字串為false if ('') { console.log('true'); } else { console.log('false'); // output: false } // 其他字串均為true if ('0') { console.log('true'); // output: true } else { console.log('false'); } if ('false') { console.log('true'); // output: true } else { console.log('false'); }
undefined和null轉為布林型別
當undefined和null在邏輯環境中執行時,都被認為是false,看下面程式碼:
// undefined和null都為false if (undefined) { console.log('true'); } else { console.log('false'); // output: false } if (null) { console.log('true'); } else { console.log('false'); // output: false }
物件轉為布林型別(from object)
當物件在邏輯環境中執行時,只要當前引用的物件不為空,都會被認為是true。如果一個物件的引用為null,根據上面的介紹,會被轉換為false。雖然使用typeof檢測null為”object”,但它並不是嚴格意義上的物件型別,只是一個物件空引用的標識。
另外,我們這裡的邏輯環境不包括比較操作符(==),因為它會根據valueOf()和toString()將物件轉為其他型別。
現在我們來看一下物件型別的示例:
// 字面量物件 var o = { valueOf: function() { return false; }, toString: function() { return false; } }; if (o) { console.log('true'); // output: true } else { console.log('false'); } // 函式 var fn = function() { return false; }; if (fn) { console.log('true'); // output: true } else { console.log('false'); } // 陣列 var ary = []; if (ary) { console.log('true'); // output: true } else { console.log('false'); } // 正則表示式 var regex = /./; if (regex) { console.log('true'); // output: true } else { console.log('false'); }
可以看到,上面的物件都被認為是true,無論內部如何定義,都不會影響最終的結果。
正是由於物件總被認為是true,使用基礎型別的包裝類時,要特別小心:
// 以下包裝物件都被認為是true if (new Boolean(false)) { console.log('true'); // output: true } else { console.log('false'); } if (new Number(0)) { console.log('true'); // output: true } else { console.log('false'); } if (new Number(NaN)) { console.log('true'); // output: true } else { console.log('false'); } if (new String('')) { console.log('true'); // output: true } else { console.log('false'); }
根據們上面介紹的,它們對應的基礎型別都會被轉為false,但使用包裝類例項的時候,引擎只會判斷其引用是否存在,不會判斷內部的值,這一點初學者需要多多注意。當然我們也可以不使用new關鍵字,而是顯示的呼叫其包裝類函式,將這些值轉為布林型別:
if (Boolean(false)) { console.log('true'); } else { console.log('false'); // output: false } if (Number(0)) { console.log('true'); } else { console.log('false'); // output: false } if (Number(NaN)) { console.log('true'); } else { console.log('false'); // output: false } if (String('')) { console.log('true'); } else { console.log('false'); // output: false }
對於Boolean類,有一個特別需要注意的是,當傳入一個字串時,它不會去解析字串內部的值,而是做個簡單地判斷,只要不是空字串,都會被認為是true:
if (Boolean('false')) { console.log('true'); // output: true } else { console.log('false'); } if (Boolean('')) { console.log('true'); } else { console.log('false'); // output: false }
上面介紹了這麼多,還有幾個例子需要提一下,那就是邏輯非、邏輯與和邏輯或操作符,連用兩個邏輯非可以把一個值轉為布林型別,而使用邏輯與和邏輯或時,根據上面的規則,參與運算的值會被轉換為相對應的布林型別:
// 下面幾個轉為false var isFalse = !!0; // false var isFalse = !!NaN; // false var isFalse = !!''; // false var isFalse = !!undefined; // false var isFalse = !!null; // false // 下面都轉為true var isTrue = !!3; // true var isTrue = !!-3; // true var isTrue = !!'0'; // true var isTrue = !!{}; // true // 邏輯與 var foo = 0 && 3; // 0 var foo = -3 && 3; // 3 // 邏輯或 var foo = 0 || 3; // 3 var foo = -3 || 3; // -3
轉為數字型別
運算元在數字環境中參與運算時,會被轉為相對應的數字型別值,其中的轉換規則如下:
一個其他型別的值被轉換為數字,跟其參與運算的操作符有很密切的聯絡,下面我們就來詳細介紹:
當加號“+”作為一元操作符時,引擎會試圖將運算元轉換為數字型別,如果轉型失敗,則會返回NaN,程式碼如下所示:
var foo = +''; // 0 var foo = +'3'; // 3 var foo = +'3px'; // NaN var foo = +false; // 0 var foo = +true; // 1 var foo = +null; // 0 var foo = +undefined; // NaN
上面程式碼中,對於不符合數字規則的字串,和直接呼叫Number()函式效果相同,但和parseInt()有些出入:
var foo = Number('3px'); // NaN var foo = parseInt('3px'); // 3
可以看出,parseInt對字串引數比較寬容,只要起始位置符合數字型別標準,就逐個解析,直到遇見非數字字元為止,最後返回已解析的數字部分,轉為數字型別。
當加號“+”作為二元操作符時,我們上面也提到過,如果一個運算元為字串,則加號“+”作為字串連線符,但如果兩個運算元都不是字串型別,則會作為加法操作符,執行加法操作,這個時候,其他資料型別也會被轉為數字型別:
var foo = true + 1; // 2 var foo = true + false; // 1 var foo = true + null; // 1 var foo = null + 1; // 1 var foo = null + undefined; // NaN var foo = null + NaN; // NaN
上面加法運算過程中都出現了型別轉換,true轉為1,false轉為0,null轉為0,undefined轉為NaN,最後一個例子中,null和NaN運算時,是先轉為0,然後參與運算,NaN和任何其他數字型別運算時都會返回NaN,所以最終這個結果還是NaN。
對於undefined轉為NaN似乎很好理解,但為什麼null會轉為0呢?這裡也有些歷史淵源的,熟悉C的朋友都知道,空指標其實是設計為0值的:
// 空指標的值為0 int *p = NULL; if (p == 0) { printf("NULL is 0"); // output: NULL is 0 }
程式語言的發展是有規律的,語言之間也存在著密切的關聯,新的語言總是會沿用老的傳統,繼而新增一些新的特性。從上面的例子中,我們發現,null被轉為0其實很好理解,一點也不奇怪。
另外,我們可別忘了減號“-”操作符,當減號“-”作為一元操作符(unary negation)時,也會將運算元轉換為數字,只不過轉換的結果與上面相反,合法的數字都被轉為負值。
除加號“+”以外的其他二元操作符,都會將運算元轉為數字,字串也不例外(如果轉型失敗,則返回NaN繼續參與運算):
var foo = '5' - '2'; // 3 var foo = '5' * '2'; // 10 var foo = '5' / '2'; // 2.5 var foo = '5' % '2'; // 1 var foo = '5' << '1'; // 10 var foo = '5' >> '1'; // 2 var foo = '5' ** '2'; // 25 var foo = '5' * true; // 5 var foo = '5' * null; // 0 var foo = '5' * undefined; // NaN var foo = '5' * NaN; // NaN
上面的操作符中,位移和求冪操作符平時用的不多,不過在某些場景下(比如演算法中)還是挺實用的。我們都知道,JavaScript中的數字型別都以浮點型儲存,這就意味著我們不能想C和Java那樣直接求整除結果,而是通過相關的函式進一步處理實現的,如果通過位移可以簡化不少,而求冪操作也可以直接通過求冪運算子算出結果,看下面程式碼:
// 浮點型運算 var foo = 5 / 2; // 2.5 // 整除操作 var foo = Math.floor(5 / 2); // 2 // 向右移一位實現整除 var foo = 5 >> 1; // 2 // 求冪函式 var foo = Math.pow(5, 2); // 25 // 求冪運算 var foo = 5 ** 2; // 25
除了上面的操作符之外,遞增和遞減操作符也會將運算元轉為數字,下面以字首遞增操作符為例:
var foo = ''; ++foo; // foo: 1 var foo = '3'; ++foo; // foo: 4 var foo = true; ++foo; // foo: 2 var foo = null; ++foo; // foo: 1 var foo = undefined; ++foo; // foo: NaN var foo = '3px'; ++foo; // foo: NaN
上面就是基本資料型別在數字環境下的轉換規則。對於物件型別,同樣有一套轉換機制,我們上面也提到了,valueOf()方法和toString()方法會在不同的時機被呼叫,進而得到相應的返回值,最後根據返回值再進行型別轉換,將其轉為目標型別。由於篇幅限制,關於自動型別轉換的後續內容,博主安排在下一篇中講解,敬請期待。
轉載自: