咱們都知道,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()方法,最後將返回值轉爲字符串類型。上面咱們定義了一個對象,包含valueOf()和toString()方法,而後和一個空字符串進行運算,能夠看得出來,它是調用了valueOf()方法,而後咱們重寫此對象,將valueOf()移除,也就是不重寫object的valueOf()方法,從最後的結果來看,它最終是調用了toString()方法,而後將返回的數字類型5與空字符串進行運算,最終獲得一個字符串類型的值。數組
當數字在邏輯環境中執行時,會自動轉爲布爾類型。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,這一點須要注意。函數
和數字相似,當字符串在邏輯環境中執行時,也會被轉爲布爾類型。空字符串會被轉爲false,其它字符串都會轉爲true,代碼以下:spa
// 空字符串爲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在邏輯環境中執行時,都被認爲是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 }
當對象在邏輯環境中執行時,只要當前引用的對象不爲空,都會被認爲是true。若是一個對象的引用爲null,根據上面的介紹,會被轉換爲false。雖然使用typeof檢測null爲"object",但它並非嚴格意義上的對象類型,只是一個對象空引用的標識。指針
另外,咱們這裏的邏輯環境不包括比較操做符(==),由於它會根據valueOf()和toString()將對象轉爲其餘類型。code
如今咱們來看一下對象類型的示例:
// 字面量對象 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
操做數在數字環境中參與運算時,會被轉爲相對應的數字類型值,其中的轉換規則以下:
i. 字符串類型轉爲數字(from string): 空字符串被轉爲0,非空字符串中,符合數字規則的會被轉換爲對應的數字,不然視爲NaN
ii. 布爾類型轉爲數字(from boolean): true被轉爲1,false被轉爲0
iii. null被轉爲0,undefined被轉爲NaN
iv. 對象類型轉爲數字(from object): valueOf()方法先試圖被調用,若是調用返回的結果爲基礎類型,則再將其轉爲數字,若是返回結果不是基礎類型,則會再試圖調用toString()方法,最後試圖將返回結果轉爲數字,若是這個返回結果是基礎類型,則會獲得一個數字或NaN,若是不是基礎類型,則會拋出一個異常
一個其餘類型的值被轉換爲數字,跟其參與運算的操做符有很密切的聯繫,下面咱們就來詳細介紹:
當加號「+」做爲一元操做符(unary)時,引擎會試圖將操做數轉換爲數字類型,若是轉型失敗,則會返回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()方法會在不一樣的時機被調用,進而獲得相應的返回值,最後根據返回值再進行類型轉換,將其轉爲目標類型。因爲篇幅限制,關於自動類型轉換的後續內容,博主安排在下一篇中講解,敬請期待。
參考資料:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators
http://jibbering.com/faq/notes/type-conversion/
http://stackoverflow.com/questions/18808226/why-is-typeof-null-object