原文地址:阿木木的博客
前言
說實話,JavaScript 的類型轉(zhuǎn)換是個相當(dāng)頭疼的問題蜜另,無論是對于初學(xué)者還是有經(jīng)驗的老司機适室。它的難處并不在于概念多難理解,而是情況多且雜举瑰,看似相同的情況結(jié)果卻又出人意料捣辆,很少有人能保證時刻都能做出正確的判斷。
因此此迅,這篇文章希望能講的足夠細致和明確汽畴,讓大家能夠在日常使用中,能夠盡快的搞清楚類型轉(zhuǎn)換的順序和結(jié)果耸序。
長文預(yù)警忍些,建議先 mark, 分多次查看。
一坎怪、類型轉(zhuǎn)換
1. 什么叫類型轉(zhuǎn)換罢坝?
我們知道,JavaScript 中存在七種數(shù)據(jù)類型搅窿,在必要的時候炸客,我們會對不同類型的值進行相互間的轉(zhuǎn)換。比如說戈钢,在進行條件判斷時痹仙,我們需要將其他類型的值轉(zhuǎn)為布爾類型值,在使用 console.log()
打印內(nèi)容時殉了,需要將其轉(zhuǎn)為字符串輸出开仰。
2. JavaScript 中的類型轉(zhuǎn)換方式有哪些?
在 JavaScript 中,分為顯式類型轉(zhuǎn)換和隱式類型轉(zhuǎn)換众弓。
其中恩溅,顯式類型轉(zhuǎn)換是我們?yōu)榱斯δ苄枰藶榈膶⒁环N類型的值轉(zhuǎn)換為另一中類型谓娃,轉(zhuǎn)換的時機和結(jié)果都是我們預(yù)期的脚乡;而隱式類型轉(zhuǎn)換則是 JavaScript 在代碼運行時,未經(jīng)我們允許而進行的強制類型轉(zhuǎn)換滨达。
二奶稠、顯式類型轉(zhuǎn)換
1. 其他類型轉(zhuǎn)換為字符串
值類型 | 例子 | 轉(zhuǎn)換后 | 調(diào)用法則 |
---|---|---|---|
number | 34 | '34' | String(34) |
boolean | true | 'true' | String(true) |
boolean | false | 'false' | String(false) |
undefiend | undefined | 'undefined' | String(undefined) |
null | null | 'null' | String(null) |
object | { a: 'fa' } | "[object Object]" | String({a: 'fa'}) |
object | new String(45) | '45' | String(new String(45)) |
object | [1, 2] | '1,2' | String([1,2]) |
object | function() {var d;} | "function() { var d; }" | String(function() {var d;}) |
其他類型的值轉(zhuǎn)換為字符串,是通過調(diào)用原生函數(shù)String()
實現(xiàn)捡遍,但不同類型值的實現(xiàn)卻有明顯的差異锌订。
對于基本類型的值,直接將其轉(zhuǎn)化為值的字符串形式画株。而對于對象類型來說辆飘,便有些復(fù)雜了。
首先谓传,每個對象內(nèi)部都有一個 [[Class]]
屬性蜈项,我們通過Object.prototype.toString()
方法可以得到這個屬性的字符串值。
對于對象(如{ a: 'ff'; }
)而言续挟,除非自己定義 toString()
方法战得,否則,調(diào)用 String()
方法將返回和調(diào)用 Object.prototype.toString()
相同的值庸推。(如 : "[object Object]"
)。
const obj_1 = {
b: 'lalala'
};
const obj_2 = {
toString() {
return "fasfa";
}
};
String(obj_1); // '[object Object]'
String(obj_2); // 'fasfa'
其次浇冰, JavaScript 中贬媒,除了普通對象,還有以下幾種:
-
封裝對象
對于基本類型值
string
肘习、number
际乘、boolean
是沒有.length
及toString()
方法的,因此漂佩,JavaScript 提供了內(nèi)建函數(shù)String()
脖含、Number()
、Boolean()
投蝉,通過new
調(diào)用后會將基本類型值封裝為一個對象养葵。如果想要取到封裝對象中的基本類型值,可以使用
valueOf()
方法瘩缆。// string 類型 const a = 'i am string'; typeof a; // 'string' // string 封裝對象 const b = new String('i am sringObject'); typeof b; // 'object' // 拆封 b.valueOf(); // i am sringObject
那對于封裝對象关拒,
String()
會返回什么值呢?事實上,封裝對象對于
toString()
方法進行了封裝着绊,因此谐算,對封裝對象調(diào)用String()
方法,將會返回封裝對象調(diào)用toString()
方法返回的值归露。const numObj = new Number(false); // Number {0} numObj.toString(); // '0' String(numObj); // '0'
-
函數(shù)
對于函數(shù)來說洲脂,它也包裝了自己的
toString()
方法,因此剧包,調(diào)用String()
方法時將返回函數(shù)字符串化后的值恐锦。function bar() { console.log('bar'); } String(bar); // "function bar() {? console.log('bar');?}" bar.toString(); // "function bar() {? console.log('bar');?}" Object.prototype.toString.call(bar); // "[object Function]"
從上例可以看到,
String()
與toString()
方法調(diào)用的是函數(shù)自己封裝的toSring()
玄捕,如果調(diào)用對象的toString()
方法踩蔚,則函數(shù)與普通對象一樣,返回的是函數(shù)對象內(nèi)部的[[Class]]
屬性枚粘。 -
數(shù)組
數(shù)組同函數(shù)一樣馅闽,同樣包裝了自己的
toString()
方法。此方法會將數(shù)組中的每一項用逗號連接成一個字符串馍迄。const arr = [1,4,6]; String(arr); // "1,4,6" arr.toString(); // "1,4,6" Object.prototype.toString.call(arr); // "[object Array]"
2. 其他類型值轉(zhuǎn)為數(shù)字
同樣福也,先感受一下什么叫絕望?~~
值類型 | 例子 | 轉(zhuǎn)換后 | 調(diào)用法則 |
---|---|---|---|
string | '34' | 34 | Number('34') |
string | '' | 0 | Number('') |
string | '34fad' | NaN | Number('34fad') |
string | '34fad'攀圈、'34.24'暴凑、'34' | 34 | parseInt('34fad') |
string | '34fad'、'34' | 34 | parseFloat(值) |
string | '34.34' | 34.34 | parseFloat(值) |
boolean | true | 1 | Number(true) |
boolean | false | 0 | Number(false) |
undefiend | undefined | NaN | Number(undefined) |
null | null | 0 | Number(null) |
object | { a: 'fa' } | NaN | Number({a: 'fa'}) |
object | new String('fff') | NaN | Number(new String('fff')) |
object | [] | 0 | Number([]) |
object | [1, 2] | NaN | Number([1,2]) |
object | function() {var d;} | NaN | Number(function() {var d;}) |
看完一臉懵逼有沒有赘来?现喳!哈哈,不用害怕犬辰,乍看上去嗦篱,大概會覺得異常混亂幌缝,其實稍加整理灸促,不外乎以下幾種情況:
-
轉(zhuǎn)換后值為 NaN
數(shù)字與字符串不同,并不是任何類型值都能轉(zhuǎn)為數(shù)字涵卵,因此浴栽,就會有 NaN,意思就是
not a number
轿偎。諸如包含非數(shù)字的字符串典鸡、undefined、非空數(shù)組坏晦,部分對象椿每,都是我們知道無法轉(zhuǎn)化為一個數(shù)字的伊者。
-
boolean 類型值
對于
true
和false
,true
轉(zhuǎn)換為 1间护,false
轉(zhuǎn)為 0亦渗。 -
帶有數(shù)字的字符串
從上面我們可以看到,對于帶有數(shù)字的字符串汁尺,有三種方法進行轉(zhuǎn)換法精,但規(guī)則不同。
Number()
方法會對字符串整體進行轉(zhuǎn)換痴突, 它會先判斷這個字符串是否是個正確的數(shù)字字符串搂蜓,如果不是,則會返回NaN
辽装。parseInt()
方法則會對字符串從左往右依次解析帮碰,直到遇到第一個非數(shù)字字符(包括小數(shù)點),如果最左邊的字符是非數(shù)字字符拾积,則返回NaN
殉挽。parseFloat()
方法解析順序同parseInt()
相同,不同的是它遇到第一個小數(shù)點時會正常往右繼續(xù)解析拓巧,直至遇到非數(shù)字字符停止斯碌。
其實嚴(yán)格來講,只有
Number()
方法是進行轉(zhuǎn)換操作肛度,而后兩者屬于將字符串解析 為數(shù)字傻唾,但為了講解方便,我將它們放在一起講述承耿。 -
對象
對于對象而言冠骄,會先將對象轉(zhuǎn)為基本類型值,再對基本類型值調(diào)用
Number()
方法加袋。那如何將對象轉(zhuǎn)為基本類型值凛辣?首先會調(diào)用對象的
valueOf()
方法,如果沒有此方法或者此方法返回值不是基本類型值锁荔,則會調(diào)用toString()
方法,如果toString()
方法不存在或者返回值也不是基本類型值蝙砌,會產(chǎn)生TypeError
錯誤阳堕。// 普通對象 const nomalObj = { a: '56' }; nomalObj .valueOf(); // { a: '56'} nomalObj.toString(); // "[object Object]" // Number(nomalObj) 相當(dāng)于Number("[object Object]") Number("[object Object]"); // NaN Number(nomalObj); // NaN // valueOf() 返回基本類型值的對象 const obj_1 = { a: '56', valueOf: function() { return '23'; } }; obj_1.valueOf(); // '23' // Number(obj_1) 相當(dāng)于 Number('23'); Number('23'); // 23 Number(obj_1); // 23 // valueOf() 返回非基本類型值,toString() 返回基本類型值的對象 const obj_2 = { a: '56', valueOf: function() { return {b: 34} }, toString: function() { return false; } }; obj_2.valueOf(); // {b: 34} obj_2.toString(); // false // Number(obj_2) 相當(dāng)于 Number(false) Number(obj_2); // 0 Number(false); // 0
上面的規(guī)則择克,適用于我們所說的所有對象恬总,比如數(shù)組,封裝對象和函數(shù)肚邢。
3. 其他類型轉(zhuǎn)換為 boolean 值
我們可以通過 Boolean()
方法 或!!
運算符來顯式的將一個值轉(zhuǎn)換為布爾值壹堰。
相對來說拭卿,判斷一個值是 true
還是 false
則比較容易,我們只需要記住以下幾種值會轉(zhuǎn)換為 false
贱纠,而其他值峻厚,均為 true
。
- undefined
- null
- false
- +0谆焊、-0 和 NaN
- ""
當(dāng)我們看到 []惠桃、{}
甚至是 "''"
時,也一定要記住辖试,它們是真值辜王。
Boolean(false); // fasle
Boolean([]); //true
Boolean({}); //true
Boolean(''); // false
Boolean('""'); // true
Boolean('false'); // true
三、隱式強制類型轉(zhuǎn)換
除了進行強制類型轉(zhuǎn)換罐孝,JavaScript 會在運行時根據(jù)需要呐馆,自動進行類型的轉(zhuǎn)換,盡管這個特點飽受爭議莲兢,但不得不承認(rèn)汹来,某些情況下我們?nèi)耘f更喜歡使用某些隱式轉(zhuǎn)換規(guī)則。
一旦某些隱式的規(guī)則被接受并廣泛使用怒见,從某種意義上來講俗慈,這些規(guī)則便同顯式轉(zhuǎn)換一樣。
1. 奇怪的 +
號
先看一一個最常見的例子:
const a = 5;
const b = '6';
console.log(a+a); // 10
console.log(a+b); // '56'
console.log(b+b); // '66'
之所以會產(chǎn)生上例中的狀況遣耍,原因就在于在JavaScript 中闺阱,+
運算符既可以作用于number
類型值,也可以作用于 string
類型值舵变。前者進行數(shù)字相加酣溃,后者則進行字符串的拼接。
這就是為什么5 + 5 = 10
而 '6' + '6' = '66'
纪隙。而當(dāng) +
號兩邊既有數(shù)字也有字符串時赊豌,則會隱式的將數(shù)字轉(zhuǎn)換為字符串,然后進行字符串的拼接绵咱。
那兩邊沒有字符串的情況呢碘饼?比如:
const a = [1,4];
const b = [2,3];
const c = 4;
console.log(a+c); // '1,44'
console.log(a+b); // '1,42,3'
為什么會這樣?原來只要+
的其中一個操作數(shù)可以通過某種方式(toPrimitive
)轉(zhuǎn)換為字符串悲伶,就會進行字符串的拼接艾恼。
我們知道,數(shù)組[1,4]
可以通過 toString()
方法返回字符串 '1,4'
麸锉,因此钠绍,[1,4] + 4
就相當(dāng)于 '1,4' + 4
。
因為這個特性花沉,我們在想將一個數(shù)字 a 轉(zhuǎn)換為字符串時柳爽,便可以直接使用 a + ''
的形式即可媳握。相對于顯式使用String(a)
,隱式轉(zhuǎn)換則更加簡潔磷脯。
從數(shù)組的例子我們可以看到蛾找,除了數(shù)字,其他類型的值也可以通過 + ' '
的形式轉(zhuǎn)化為字符串争拐。
const a = {b: '2'}
console.log( a+ ''); // "[object Object]"
但有一點需要注意腋粥,對于對象而言,使用 String()
方法是直接取這個對象 toString()
方法的返回值架曹,而 + ' '
,則會對這個對象調(diào)用 valueOf
反法隘冲,然后對 valueOf
的返回值調(diào)用 toString()
,將其轉(zhuǎn)換為字符串绑雄。
const a = {
toString: function() { return 45 },
valueOf: function() { return 4}
};
String(a); // '45'
a + ' '; // // '4'
好在除非我們特意去改變一個對象的 valueOf
及 'toString()' 方法展辞,通過上述兩個方式的轉(zhuǎn)換后的結(jié)果都是一致的。
2. 有用的 -
號
與 +
號不同的是万牺,-
號只能用于數(shù)字的相減罗珍,對于它兩邊的操作數(shù),都會經(jīng)過隱式類型轉(zhuǎn)換轉(zhuǎn)為數(shù)字脚粟。
const a = '34';
const b = '4';
console.log(a - b); // 30
const c = 'dd';
console.log(a - c); // NaN
const d = [4];
console.log(a - d); // 30
根據(jù)上例覆旱,我們可看到,如果 -
號兩邊是字符串核无,則會將他們強制轉(zhuǎn)換為數(shù)字扣唱,如果 -
兩邊不是字符串,則會先將其轉(zhuǎn)為字符串团南,再將這個字符串轉(zhuǎn)為數(shù)字噪沙。
3. 隱式轉(zhuǎn)換為布爾值
將其他類型值隱式轉(zhuǎn)換為布爾值是我們最常用的一種轉(zhuǎn)換。因為程序的編寫實質(zhì)上就是不停的進行判斷吐根。
在以下場景中正歼,都是進行判斷,而只要傳入的值不是布爾值拷橘,都會通過隱式類型轉(zhuǎn)換轉(zhuǎn)為布爾值局义。
-
if (..) {}
語句中的條件判斷表達式。 -
for ( .. ; .. ; ..)
語句中的條件判斷表達式冗疮。 -
while (..)
和do ... while ( ..)
中的條件判斷表達式萄唇。 -
? :
中的條件判斷表達式。 - 邏輯或
||
或邏輯與&&
左邊的操作數(shù)赌厅。
在這些情況下穷绵,都將會進行其他類型值到布爾類型值的隱式轉(zhuǎn)換轿塔,規(guī)則同顯式調(diào)用 Boolean()
特愿。
完
上面就是不同數(shù)據(jù)類型直接顯式或隱式的轉(zhuǎn)換規(guī)則仲墨,我們不需要將每一種情況都牢記在心,但有必要對他們進行充分的了解揍障,這可以保證我們在實際寫代碼時避免不少奇怪又難以排查的 bug 目养。