JavaScript 數(shù)據(jù)類型轉(zhuǎn)換

2021-09-15 15:12 更新

JavaScript是一種動(dòng)態(tài)類型語言,變量是沒有類型的,可以隨時(shí)賦予任意值。但是,數(shù)據(jù)本身和各種運(yùn)算是有類型的,因此運(yùn)算時(shí)變量需要轉(zhuǎn)換類型。大多數(shù)情況下,這種數(shù)據(jù)類型轉(zhuǎn)換是自動(dòng)的,但是有時(shí)也需要手動(dòng)強(qiáng)制轉(zhuǎn)換。

強(qiáng)制轉(zhuǎn)換

強(qiáng)制轉(zhuǎn)換主要指使用Number、String和Boolean三個(gè)構(gòu)造函數(shù),手動(dòng)將各種類型的值,轉(zhuǎn)換成數(shù)字、字符串或者布爾值。

Number函數(shù):強(qiáng)制轉(zhuǎn)換成數(shù)值

使用Number函數(shù),可以將任意類型的值轉(zhuǎn)化成數(shù)字。

(1)原始類型值的轉(zhuǎn)換規(guī)則

  • 數(shù)值:轉(zhuǎn)換后還是原來的值。

  • 字符串:如果可以被解析為數(shù)值,則轉(zhuǎn)換為相應(yīng)的數(shù)值,否則得到NaN??兆址D(zhuǎn)為0。

  • 布爾值:true轉(zhuǎn)成1,false轉(zhuǎn)成0。

  • undefined:轉(zhuǎn)成NaN。

  • null:轉(zhuǎn)成0。
Number("324") // 324

Number("324abc") // NaN

Number("") // 0

Number(false) // 0

Number(undefined) // NaN

Number(null) // 0

Number函數(shù)將字符串轉(zhuǎn)為數(shù)值,要比parseInt函數(shù)嚴(yán)格很多?;旧?,只要有一個(gè)字符無法轉(zhuǎn)成數(shù)值,整個(gè)字符串就會(huì)被轉(zhuǎn)為NaN。

parseInt('011') // 9
parseInt('42 cats') // 42
parseInt('0xcafebabe') // 3405691582

Number('011') // 11
Number('42 cats') // NaN
Number('0xcafebabe') // 3405691582

上面代碼比較了Number函數(shù)和parseInt函數(shù),區(qū)別主要在于parseInt逐個(gè)解析字符,而Number函數(shù)整體轉(zhuǎn)換字符串的類型。另外,Number會(huì)忽略八進(jìn)制的前導(dǎo)0,而parseInt不會(huì)。

Number函數(shù)會(huì)自動(dòng)過濾一個(gè)字符串前導(dǎo)和后綴的空格。

Number('\t\v\r12.34\n ')

(2)對象的轉(zhuǎn)換規(guī)則

對象的轉(zhuǎn)換規(guī)則比較復(fù)雜。

  1. 先調(diào)用對象自身的valueOf方法,如果該方法返回原始類型的值(數(shù)值、字符串和布爾值),則直接對該值使用Number方法,不再進(jìn)行后續(xù)步驟。

  2. 如果valueOf方法返回復(fù)合類型的值,再調(diào)用對象自身的toString方法,如果toString方法返回原始類型的值,則對該值使用Number方法,不再進(jìn)行后續(xù)步驟。

  3. 如果toString方法返回的是復(fù)合類型的值,則報(bào)錯(cuò)。
Number({a:1})
// NaN

上面代碼等同于

if (typeof {a:1}.valueOf() === 'object'){
    Number({a:1}.toString());
} else {
    Number({a:1}.valueOf());
}

上面代碼的valueOf方法返回對象本身({a:1}),所以對toString方法的返回值“[object Object]”使用Number方法,得到NaN。

如果toString方法返回的不是原始類型的值,結(jié)果就會(huì)報(bào)錯(cuò)。

var obj = {
    valueOf: function () {
            console.log("valueOf");
            return {};
    },
    toString: function () {
            console.log("toString");
            return {}; 
    }
};

Number(obj)
// TypeError: Cannot convert object to primitive value

上面代碼的valueOf和toString方法,返回的都是對象,所以轉(zhuǎn)成數(shù)值時(shí)會(huì)報(bào)錯(cuò)。

從上面的例子可以看出,valueOf和toString方法,都是可以自定義的。

Number({valueOf:function (){return 2;}})
// 2

Number({toString:function(){return 3;}})
// 3

Number({valueOf:function (){return 2;},toString:function(){return 3;}})
// 2

上面代碼對三個(gè)對象使用Number方法。第一個(gè)對象返回valueOf方法的值,第二個(gè)對象返回toString方法的值,第三個(gè)對象表示valueOf方法先于toString方法執(zhí)行。

String函數(shù):強(qiáng)制轉(zhuǎn)換成字符串

使用String函數(shù),可以將任意類型的值轉(zhuǎn)化成字符串。規(guī)則如下:

(1)原始類型值的轉(zhuǎn)換規(guī)則

  • 數(shù)值:轉(zhuǎn)為相應(yīng)的字符串。

  • 字符串:轉(zhuǎn)換后還是原來的值。

  • 布爾值:true轉(zhuǎn)為“true”,false轉(zhuǎn)為“false”。

  • undefined:轉(zhuǎn)為“undefined”。

  • null:轉(zhuǎn)為“null”。
String(123) // "123"

String("abc") // "abc"

String(true) // "true"

String(undefined) // "undefined"

String(null) // "null"

(2)對象的轉(zhuǎn)換規(guī)則

如果要將對象轉(zhuǎn)為字符串,則是采用以下步驟。

  1. 先調(diào)用toString方法,如果toString方法返回的是原始類型的值,則對該值使用String方法,不再進(jìn)行以下步驟。

  2. 如果toString方法返回的是復(fù)合類型的值,再調(diào)用valueOf方法,如果valueOf方法返回的是原始類型的值,則對該值使用String方法,不再進(jìn)行以下步驟。

  3. 如果valueOf方法返回的是復(fù)合類型的值,則報(bào)錯(cuò)。

String方法的這種過程正好與Number方法相反。

String({a:1})
// "[object Object]"

上面代碼相當(dāng)于下面這樣。

String({a:1}.toString())
// "[object Object]"

如果toString方法和valueOf方法,返回的都不是原始類型的值,則String方法報(bào)錯(cuò)。

var obj = {
    valueOf: function () {
            console.log("valueOf");
            return {}; 
    },
    toString: function () {
            console.log("toString");
            return {}; 
    }
};

String(obj)
// TypeError: Cannot convert object to primitive value

下面是一個(gè)自定義toString方法的例子。

String({toString:function(){return 3;}})
// "3"

String({valueOf:function (){return 2;}})
// "[object Object]"

String({valueOf:function (){return 2;},toString:function(){return 3;}})
// "3"

上面代碼對三個(gè)對象使用String方法。第一個(gè)對象返回toString方法的值(數(shù)值3),然后對其使用String方法,得到字符串“3”;第二個(gè)對象返回的還是toString方法的值("[object Object]"),這次直接就是字符串;第三個(gè)對象表示toString方法先于valueOf方法執(zhí)行。

Boolean函數(shù):強(qiáng)制轉(zhuǎn)換成布爾值

使用Boolean函數(shù),可以將任意類型的變量轉(zhuǎn)為布爾值。

(1)原始類型值的轉(zhuǎn)換方法

以下六個(gè)值的轉(zhuǎn)化結(jié)果為false,其他的值全部為true。

  • undefined
  • null
  • -0
  • +0
  • NaN
  • ''(空字符串)
Boolean(undefined) // false

Boolean(null) // false

Boolean(0) // false

Boolean(NaN) // false

Boolean('') // false

(2)對象的轉(zhuǎn)換規(guī)則

所有對象的布爾值都是true,甚至連false對應(yīng)的布爾對象也是true。

Boolean(new Boolean(false))
// true

請注意,空對象{}和空數(shù)組[]也會(huì)被轉(zhuǎn)成true。

Boolean([]) // true

Boolean({}) // true

自動(dòng)轉(zhuǎn)換

當(dāng)遇到以下幾種情況,JavaScript會(huì)自動(dòng)轉(zhuǎn)換數(shù)據(jù)類型:

  • 不同類型的數(shù)據(jù)進(jìn)行互相運(yùn)算;

  • 對非布爾值類型的數(shù)據(jù)求布爾值;

  • 對非數(shù)值類型的數(shù)據(jù)使用一元運(yùn)算符(即“+”和“-”)。

自動(dòng)轉(zhuǎn)換為布爾值

當(dāng)JavaScript遇到預(yù)期為布爾值的地方(比如if語句的條件部分),就會(huì)將非布爾值的參數(shù)自動(dòng)轉(zhuǎn)換為布爾值。它的轉(zhuǎn)換規(guī)則與上面的“強(qiáng)制轉(zhuǎn)換成布爾值”的規(guī)則相同,也就是說,在預(yù)期為布爾值的地方,系統(tǒng)內(nèi)部會(huì)自動(dòng)調(diào)用Boolean方法。

因此除了以下六個(gè)值,其他都是自動(dòng)轉(zhuǎn)為true:

  • undefined
  • null
  • -0
  • +0
  • NaN
  • ''(空字符串)
if (!undefined && !null && !0 && !NaN && !''){
    console.log('true');
}
// true

自動(dòng)轉(zhuǎn)換為字符串

當(dāng)JavaScript遇到預(yù)期為字符串的地方,就會(huì)將非字符串的數(shù)據(jù)自動(dòng)轉(zhuǎn)為字符串,轉(zhuǎn)換規(guī)則與“強(qiáng)制轉(zhuǎn)換為字符串”相同。

字符串的自動(dòng)轉(zhuǎn)換,主要發(fā)生在加法運(yùn)算時(shí)。當(dāng)一個(gè)值為字符串,另一個(gè)值為非字符串,則后者轉(zhuǎn)為字符串。

'5' + 1 // '51'
'5' + true // "5true"
'5' + false // "5false"
'5' + {} // "5[object Object]"
'5' + [] // "5"
'5' + function (){} // "5function (){}"
'5' + undefined // "5undefined"
'5' + null // "5null"

自動(dòng)轉(zhuǎn)換為數(shù)值

當(dāng)JavaScript遇到預(yù)期為數(shù)值的地方,就會(huì)將參數(shù)值自動(dòng)轉(zhuǎn)換為數(shù)值,轉(zhuǎn)換規(guī)則與“強(qiáng)制轉(zhuǎn)換為數(shù)值”相同。

除了加法運(yùn)算符有可能把運(yùn)算子轉(zhuǎn)為字符串,其他運(yùn)算符都會(huì)把兩側(cè)的運(yùn)算子自動(dòng)轉(zhuǎn)成數(shù)值。

'5' - '2' // 3
'5' * '2' // 10
true - 1  // 0
false - 1 // -1
'1' - 1   // 0
'5'*[]    // 0
false/'5' // 0
'abc'-1   // NaN

上面都是二元算術(shù)運(yùn)算符的例子,JavaScript的兩個(gè)一元算術(shù)運(yùn)算符——正號和負(fù)號——也會(huì)把運(yùn)算子自動(dòng)轉(zhuǎn)為數(shù)值。

+'abc' // NaN
-'abc' // NaN
+true // 1
-false // 0

小結(jié)

由于自動(dòng)轉(zhuǎn)換有很大的不確定性,而且不易除錯(cuò),建議在預(yù)期為布爾值、數(shù)值、字符串的地方,全部使用Boolean、Number和String方法進(jìn)行顯式轉(zhuǎn)換。

加法運(yùn)算符的類型轉(zhuǎn)化

加法運(yùn)算符(+)需要特別討論,因?yàn)樗梢酝瓿蓛煞N運(yùn)算(加法和字符連接),所以不僅涉及到數(shù)據(jù)類型的轉(zhuǎn)換,還涉及到確定運(yùn)算類型。

三種情況

加法運(yùn)算符的類型轉(zhuǎn)換,可以分成三種情況討論。

(1)運(yùn)算子之中存在字符串

兩個(gè)運(yùn)算子之中,只要有一個(gè)是字符串,則另一個(gè)不管是什么類型,都會(huì)被自動(dòng)轉(zhuǎn)為字符串,然后執(zhí)行字符串連接運(yùn)算。前面的《自動(dòng)轉(zhuǎn)換為字符串》一節(jié),已經(jīng)舉了很多例子。

(2)兩個(gè)運(yùn)算子都為數(shù)值或布爾值

這種情況下,執(zhí)行加法運(yùn)算,布爾值轉(zhuǎn)為數(shù)值(true為1,false為0)。

true + 5 // 6

true + true // 2

(3)運(yùn)算子之中存在對象

運(yùn)算子之中存在對象(或者準(zhǔn)確地說,存在非原始類型的值),則先調(diào)用該對象的valueOf方法。如果返回結(jié)果為原始類型的值,則運(yùn)用上面兩條規(guī)則;否則繼續(xù)調(diào)用該對象的toString方法,對其返回值運(yùn)用上面兩條規(guī)則。

1 + [1,2]
// "11,2"

上面代碼的運(yùn)行順序是,先調(diào)用[1,2].valueOf(),結(jié)果還是數(shù)組[1,2]本身,則繼續(xù)調(diào)用[1,2].toString(),結(jié)果字符串“1,2”,所以最終結(jié)果為字符串“11,2”。

1 + {a:1}
// "1[object Object]"

對象{a:1}的valueOf方法,返回的就是這個(gè)對象的本身,因此接著對它調(diào)用toString方法。({a:1}).toString()默認(rèn)返回字符串"[object Object]",所以最終結(jié)果就是字符串“1[object Object]”

有趣的是,如果更換上面代碼的運(yùn)算次序,就會(huì)得到不同的值。

{a:1} + 1
// 1

原來此時(shí),JavaScript引擎不將{a:1}視為對象,而是視為一個(gè)代碼塊,這個(gè)代碼塊沒有返回值,所以被忽略。因此上面的代碼,實(shí)際上等同于 {a:1};+1 ,所以最終結(jié)果就是1。為了避免這種情況,需要對{a:1}加上括號。

({a:1})+1
"[object Object]1"

將{a:1}放置在括號之中,由于JavaScript引擎預(yù)期括號之中是一個(gè)值,所以不把它當(dāng)作代碼塊處理,而是當(dāng)作對象處理,所以最終結(jié)果為“[object Object]1”。

1 + {valueOf:function(){return 2;}}
// 3

上面代碼的valueOf方法返回?cái)?shù)值2,所以最終結(jié)果為3。

1 + {valueOf:function(){return {};}}
// "1[object Object]"

上面代碼的valueOf方法返回一個(gè)空對象,則繼續(xù)調(diào)用toString方法,所以最終結(jié)果是“1[object Object]”。

1 + {valueOf:function(){return {};}, toString:function(){return 2;}}
// 3

上面代碼的toString方法返回?cái)?shù)值2(不是字符串),則最終結(jié)果就是數(shù)值3。

1 + {valueOf:function(){return {};}, toString:function(){return {};}}
// TypeError: Cannot convert object to primitive value

上面代碼的toString方法返回一個(gè)空對象,JavaScript就會(huì)報(bào)錯(cuò),表示無法獲得原始類型的值。

四個(gè)特殊表達(dá)式

有了上面這些例子,我們再進(jìn)一步來看四個(gè)特殊表達(dá)式。

(1)空數(shù)組 + 空數(shù)組

[] + []
// ""

首先,對空數(shù)組調(diào)用valueOf方法,返回的是數(shù)組本身;因此再對空數(shù)組調(diào)用toString方法,生成空字符串;所以,最終結(jié)果就是空字符串。

(2)空數(shù)組 + 空對象

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

這等同于空字符串與字符串“[object Object]”相加。因此,結(jié)果就是“[object Object]”。

(3)空對象 + 空數(shù)組

{} + []
// 0

JavaScript引擎將空對象視為一個(gè)空的代碼塊,加以忽略。因此,整個(gè)表達(dá)式就變成“+ []”,等于對空數(shù)組求正值,因此結(jié)果就是0。轉(zhuǎn)化過程如下:

+ []
// Number([])
// Number([].toString())
// Number("")
// 0

如果JavaScript不把前面的空對象視為代碼塊,則結(jié)果為字符串“[object Object]”。

({}) + []
// "[object Object]"

(4)空對象 + 空對象

{} + {}
// NaN

JavaScript同樣將第一個(gè)空對象視為一個(gè)空代碼塊,整個(gè)表達(dá)式就變成“+ {}”。這時(shí),后一個(gè)空對象的ValueOf方法得到本身,再調(diào)用toSting方法,得到字符串“[object Object]”,然后再將這個(gè)字符串轉(zhuǎn)成數(shù)值,得到NaN。所以,最后的結(jié)果就是NaN。轉(zhuǎn)化過程如下:

+ {}
// Number({})
// Number({}.toString())
// Number("[object Object]")

如果,第一個(gè)空對象不被JavaScript視為空代碼塊,就會(huì)得到“[object Object][object Object]”的結(jié)果。

({}) + {}
// "[object Object][object Object]"

({} + {})
// "[object Object][object Object]"  

console.log({} + {})
// "[object Object][object Object]"

var a = {} + {};
a
// "[object Object][object Object]"

需要指出的是,對于第三和第四種情況,Node.js的運(yùn)行結(jié)果不同于瀏覽器環(huán)境。

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

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

可以看到,Node.js沒有把第一個(gè)空對象視為代碼塊。原因是Node.js的命令行環(huán)境,內(nèi)部執(zhí)行機(jī)制大概是下面的樣子:

eval.call(this,"(function(){return {} + {}}).call(this)")

Node.js把命令行輸入都放在eval中執(zhí)行,所以不會(huì)把起首的大括號理解為空代碼塊加以忽略。

參考鏈接

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號