JavaScript系列文章:自動類型轉換
我們都知道,JavaScript是類型松散型語言,在聲明一個變量時,我們是無法明確聲明其類型的,變量的類型是根據其實際值來決定的,而且在運行期間,我們可以隨時改變這個變量的值和類型,另外,變量在運行期間參與運算時,在不同的運算環境中,也會進行相應的自動類型轉換。
自動類型轉換一般是根 運行環境 和 操作符 聯系在一起的,是一種隱式轉換,看似難以捉摸,其實是有一定規律性的,大體可以劃分為: 轉換為字符串類型 、 轉換為布爾類型 、 轉換為數字類型 。今天我們就介紹一下這幾種轉換機制。
1. 轉換為字符串類型(to string)
當 加號“+”作為二元操作符(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與空字符串進行運算,最終得到一個字符串類型的值。
2. 轉為布爾類型(to boolean)
a. 數字轉為布爾類型(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,這一點需要注意。
b. 字符串轉為布爾類型(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');
}
c. undefined和null轉為布爾類型(from undefined and 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
}
d. 對象轉為布爾類型(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
3. 轉為數字類型(to number)
操作數在數字環境中參與運算時,會被轉為相對應的數字類型值,其中的轉換規則如下:
i. 字符串類型轉為數字(from string): 空字符串被轉為0,非空字符串中,符合數字規則的會被轉換為對應的數字,否則視為NaN
ii. 布爾類型轉為數字(from boolean): true被轉為1,false被轉為0
iii. 對象類型轉為數字(from object): valueOf()方法先試圖被調用,如果調用返回的結果為基礎類型,則再將其轉為數字,如果返回結果不是基礎類型,則會進一步調用返回值的toString()方法,最后再試圖將返回結果轉為數字
iv. null被轉為0,undefined被轉為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
來自:http://www.cnblogs.com/liuhe688/p/5918589.html