將值從一種類型轉換為另一種類型通常稱為類型轉換,這是顯式的情況,隱式的情況稱為強制類型轉換。 JavaScript中的強制類型轉換總是返回標量基本類型值,如字元串、數字和布爾值,不會返回對象和函數。 類型轉換髮生在靜態類型語言的編譯階段,而強制類型轉換則發生在動態類型語言的運行時。 我們能夠從代碼中 ...
將值從一種類型轉換為另一種類型通常稱為類型轉換,這是顯式的情況,隱式的情況稱為強制類型轉換。
JavaScript中的強制類型轉換總是返回標量基本類型值,如字元串、數字和布爾值,不會返回對象和函數。
類型轉換髮生在靜態類型語言的編譯階段,而強制類型轉換則發生在動態類型語言的運行時。
我們能夠從代碼中看出哪些地方是顯式強制類型轉換,而隱式強制類型轉換則不那麼明顯,通常是某些操作產生的副作用。
1.字元串、數字和布爾值之間類型轉換的基本規則
1.1 ToString
基本類型值的字元串化規則為:null
轉換為"null",undefined
轉換為"undefined",true
轉換為"true"。
對於普通對象來說,除非自定義,否則toString()
返回內部屬性[[Class]]的值,如"[object Object]"。
var a = [1,2,3];
console.log(a.toString());//"1,2,3"
toString()
可以被顯式調用,或者在需要字元串化時自動調用。
JSON.stringify(..)
在將JSON對象序列化為字元串時也用到了ToString。
JSON.stringify(42);//"42"
JSON.stringify("42");//""42""
JSON.stringify(null);//"null"
JSON.stringify(true);//"true"
所有安全的JSON值都可以使用JSON.stringify(..)
字元串化。安全的JSON值是指能夠呈現為有效JSON格式的值。
不安全的JSON值包括undefined、function、symbol和包含迴圈引用的對象。
如果要對含有非法JSON值的對象做字元串化,或者對象中的某些值無法被序列化時,就需要定義toJSON()
方法來返回一個安全的JSON值。
如果對象中定義了toJSON()
方法,JSON字元串化時會首先調用該方法,然後用它的返回值來進行序列化。
toJSON()
返回的應該是一個適當的值(一個能夠被字元串化的安全的JSON值),可以是任何類型,然後再由JSON.stringify(..)
對其進行字元串化。
var a = {
b : 42,
c : "42",
d : [1,2,3]
};
console.log(JSON.stringify(a,["b","c"]));//"{"b":42,"c","42"}"
console.log(JSON.stringify(a,function(k,v){if(k != "c"){return v;}}));//"{"b":42,"d",[1,2,3]}"
1.2 ToNumber
ES5規範定義了抽象操作ToNumber用於將非數字值轉換為數字值。
其中true
轉換為1,false
轉換為0。undefined
轉換為NaN
,null
轉換為0。
對象(包括數組)會首先被轉換為相應的基本類型值,如果返回的是非數字的基本類型值,則再遵循以上規則將其強制轉換為數字。
為了將值轉換為相應的基本類型值,抽象操作ToPrimitive會首先檢查該值是否有valueOf()
方法,如果有並且返回基本類型值,就使用該值進行強制類型轉換。如果沒有就使用toString()
的返回值(如果存在)來進行強制類型轉換。
var a = {
valueOf: function(){
return "42";
}
};
var b = {
toString: function(){
return "42";
}
};
var c = [4,2];
c.toString = function(){
return this.join("");//"42"
};
Number(a);//42
Number(b);//42
Number(c);//42
Number("");//0
Number([]);//0
Number(["abc"]);//NaN
1.3 ToBoolean
假值
- undefined
- null
- false
- +0、-0和NaN
- ""
假值的布爾強制類型轉換結果為false
。
所有的對象都是真值,假值對象除外。
假值對象不是包裝了假值的封裝對象。
var a = new Boolean(false);
var b = new Number(0);
var c = new String("");
var d = Boolean( a && b && c );
console.log(d);//true
//d為true,說明a、b、c都為true。
瀏覽器在某些特定情況下,在常規JavaScript語法基礎上自己創建了一些外來(exotic)值,這些就是“假值對象”。
真值
真值就是假值列表之外的值。
var a = "false";
var b = "0";
var c = "''";
var d = Boolean( a && b && c );
console.log(d);//true
//所有字元串都是真值。
var a = [];//空數組
var b = {};//空對象
var c = function(){};//空函數
var d = Boolean( a && b && c );
console.log(d);//true
//[]、{}和function(){}都不在假值列表中,因此它們都是真值。
2.顯式強制類型轉換
我們在編碼時應儘可能地將類型轉換表達清楚,以免給別人留坑。類型轉換越清晰,代碼可讀性越高,更容易理解。
2.1 字元串和數字之間的顯式轉換
var a = 42;
var b = String(a);//將數字轉換為字元串
var c = "3.14";
var d = Number(c);//將字元串轉換為數字
console.log(b);//"42"
console.log(d);//3.14
var e = 5+ +c;//+c顯式地將c轉換為數字
console.log(e);//8.14
日期顯式轉換為數字
var d = new Date("Mon, 18 Aug 2014 08:53:06 CDT");
console.log(+d);//1408369986000
我們不建議對日期類型使用類型轉換,應該使用Date.now()
來獲得當前的時間戳,使用new Date(..).getTime()
來獲得指定時間的時間戳。
奇特的~運算符
//~x大致等同於-(x+1)。
~42;//-(42+1) ==> -43
//~和indexOf()一起可以將結果強制轉換為真/假值。
//JavaScript中字元串的indexOf(..)方法在字元串中搜索指定的子字元串,如果找到就返回子字元串所在的位置(從0開始),否則返回-1。
var a = "Hello World";
~a.indexOf("lo");//-4 <-- 真值!
if(~a.indexOf("lo")){//true
//找到匹配!
}
//~-1的結果為0。
~a.indexOf("ol");//0 <-- 假值!
!~a.indexOf("ol");//true
if(!~a.indexOf("ol")){//true
//沒有找到匹配!
}
字位截除
一些開發人員使用~~
來截除數字值的小數部分,~~
只適用於32位數字。
我們在使用~
和~~
進行此類轉換時需要確保其他人也能夠看得懂。
2.2 顯式解析數字字元串
var a = "42";
var b = "42px";
Number(a);//42
parseInt(a);//42
Number(b);//NaN
parseInt(b);//42
2.3 顯式轉換為布爾值
var a = "0";
var b = [];
var c = {};
var d = "";
var e = 0;
var f = null;
var g;
Boolean(a);//true
Boolean(b);//true
Boolean(c);//true
Boolean(d);//false
Boolean(e);//false
Boolean(f);//false
Boolean(g);//false
!!a;//true
!!b;//true
!!c;//true
!!d;//false
!!e;//false
!!f;//false
!!g;//false
//在if(..)..這樣的布爾值上下文中,如果沒有使用Boolean(..)和!!,就會自動隱式地進行ToBoolean轉換。
3.隱式強制類型轉換
隱式強制類型轉換的作用是減少冗餘,讓代碼更簡潔。
3.1 字元串和數字之間的轉換
var a = "42";
var b = "0";
var c = 42;
var d = 0;
a + b;//"420"
c + d;//42
var e = [1,2];
var f = [3,4];
e + f;//"1,23,4"
var g = 42;
var h = g + "";
h;//"42"
var i = "3.14";
var j = i - 0;
j;//3.14
3.2 布爾值到數字的轉換
function onlyOne(){
var sum = 0;
for(var i = 0; i<arguments.length; i++){
if(arguments[i]){
//下麵等同於 sum += Number(!!arguments[i]);
sum += arguments[i];
}
}
return sum == 1;
}
var a = true;
var b = false;
console.log(onlyOne(b,a));//true
console.log(onlyOne(b,a,b,b,b));//true
console.log(onlyOne(b,b));//false
console.log(onlyOne(b,a,b,b,b,a));//false
3.3 隱式強制類型轉換為布爾值
下麵的情況會發生布爾值隱式強制類型轉換:
if(..)
語句中的條件判斷表達式。for(..;..;..)
語句中的條件判斷表達式。while(..)
和do..while(..)
迴圈中的條件判斷表達式。? :
中的條件判斷表達式。- 邏輯運算符
||
(邏輯或)與&&
(邏輯與)左邊的操作數。
3.4 || 和&&
和其他語言不同,在JavaScript中||
和&&
的返回值是兩個操作數中的一個(且僅一個)。即選擇兩個操作數中的一個,然後返回它的值。
var a = 42;
var b = "abc";
var c = null;
a || b;//42
a && b;//"abc"
c || b;//"abc"
c && b;//null
//a || b相當於a ? a : b
//a && b相當於a ? b : a
對於||
來說,如果條件判斷結果為true
就返回第一個操作數的值,如果為false
就返回第二個操作數的值。
對於&&
來說,如果條件判斷結果為true
就返回第二個操作數的值,如果為false
就返回第一個操作數的值。
4.寬鬆相等和嚴格相等
寬鬆相等==
檢查值是否相等,嚴格相等===
檢查值和類型是否相等。
==
允許在相等比較中進行強制類型轉換,而===
不允許。
4.1 抽象相等
字元串和數字之間的相等比較
var a = 42;
var b = "42"
a === b;//false
a == b;//true
其他類型和布爾類型之間的相等比較
var a = "42";
//不要這樣用,條件判斷不成立
if(a == true){
//..
}
//也不要這樣用,條件判斷不成立
if(a === true){
//...
}
//這樣的顯式用法沒問題
if(a){
//..
}
//這樣的顯式用法更好
if(!!a){
//..
}
//這樣的顯式用法也很好
if(Boolean(a)){
//..
}
null和undefined之間的相等比較
var a = null;
var b;
a == b;//true
a == null;//true
b == null;//true
a == false;//false
b == false;//false
a == "";//false
b == "";//false
a == 0;//false
b == 0;//false
對象和非對象之間的相等比較
var a = "abc";
var b = Object(a);
a === b;//false
a == b;//true
//a == b結果為true,因為b通過ToPromitive進行強制類型轉換(也稱為"拆封"),並返回標量基本類型值"abc",與a相等。
var a = null;
var b = Object(a);
a == b;//false
var c = undefined;
var d = Object(c);
c == d;
var e = NaN;
var f = Object(a);
e == f;
//因為沒有對應的封裝對象,所以null和undefined不能夠被封裝。
//NaN能夠被封裝為數字封裝對象,但拆封之後NaN == NaN返回false,因為NaN不等於NaN。
4.2 比較少見的情況
假值的相等比較
極端情況
安全運用隱式強制類型轉換
- 如果兩邊的值中有
true
或者false
,千萬不要使用==
。 - 如果兩邊的值中有
[]
、""
或者0
,儘量不要使用==
。
這時最好用===
來避免不經意的強制類型轉換。這兩個原則可以讓我們避開幾乎所有強制類型轉換的坑。
5.抽象關係比較
var a = [42];
var b = ["43"];
a < b;//true
b < a;//false
var c = ["42"];
var d = ["043"];
c < d;//false
var e = [4,2];
var f = [0,4,3];
e < f;//false
//e轉換為"4,2",f轉換為"0,4,3",按字母順序進行比較
var a = {b:42};
var b = {b:43};
a < b;//false
a == b;//false
a > b;//false
a <= b;//true
a >= b;//true
//根據規範a <= b被處理為b < a,然後將結果反轉,因為b < a的結果是false,所以a <= b的結果是true。
參考資料:《你不知道的JavaScript》(中捲) 第四章