
在 JavaScript 中,有一部分内容,情況複雜,容易出錯,飽受争議但又應用廣泛,這便是類型轉換。
前言
将值從一種類型轉換為另一種類型通常稱為類型轉換。
ES6 前,JavaScript 共有六種資料類型:Undefined、Null、Boolean、Number、String、Object。
我們先捋一捋基本類型之間的轉換。
原始值轉布爾
我們使用 Boolean 函數将類型轉換成布爾類型,在 JavaScript 中,隻有 6 種值可以被轉換成 false,其他都會被轉換成 true。
console.log(Boolean()) // false
console.log(Boolean(false)) // false
console.log(Boolean(undefined)) // false
console.log(Boolean(null)) // false
console.log(Boolean(+0)) // false
console.log(Boolean(-0)) // false
console.log(Boolean(NaN)) // false
console.log(Boolean("")) // false
注意,當 Boolean 函數不傳任何參數時,會傳回 false。
原始值轉數字
我們可以使用 Number 函數将類型轉換成數字類型,如果參數無法被轉換為數字,則傳回 NaN。
在看例子之前,我們先看 ES5 規範 15.7.1.1 中關于 Number 的介紹:
根據規範,如果 Number 函數不傳參數,傳回 +0,如果有參數,調用
ToNumber(value)
。
注意這個
ToNumber
表示的是一個底層規範實作上的方法,并沒有直接暴露出來。
而
ToNumber
則直接給了一個對應的結果表。表如下:
參數類型結果UndefinedNaNNull+0Boolean如果參數是 true,傳回 1。參數為 false,傳回 +0Number傳回與之相等的值String這段比較複雜,看例子
讓我們寫幾個例子驗證一下:
console.log(Number()) // +0
console.log(Number(undefined)) // NaN
console.log(Number(null)) // +0
console.log(Number(false)) // +0
console.log(Number(true)) // 1
console.log(Number("123")) // 123
console.log(Number("-123")) // -123
console.log(Number("1.2")) // 1.2
console.log(Number("000123")) // 123
console.log(Number("-000123")) // -123
console.log(Number("0x11")) // 17
console.log(Number("")) // 0
console.log(Number(" ")) // 0
console.log(Number("123 123")) // NaN
console.log(Number("foo")) // NaN
console.log(Number("100a")) // NaN
如果通過 Number 轉換函數傳入一個字元串,它會試圖将其轉換成一個整數或浮點數,而且會忽略所有前導的 0,如果有一個字元不是數字,結果都會傳回 NaN,鑒于這種嚴格的判斷,我們一般還會使用更加靈活的 parseInt 和 parseFloat 進行轉換。
parseInt 隻解析整數,parseFloat 則可以解析整數和浮點數,如果字元串字首是 "0x" 或者"0X",parseInt 将其解釋為十六進制數,parseInt 和 parseFloat 都會跳過任意數量的前導空格,盡可能解析更多數值字元,并忽略後面的内容。如果第一個非空格字元是非法的數字直接量,将最終傳回 NaN:
console.log(parseInt("3 abc")) // 3
console.log(parseFloat("3.14 abc")) // 3
console.log(parseInt("-12.34")) // -12
console.log(parseInt("0xFF")) // 255
console.log(parseFloat(".1")) // 0.1
console.log(parseInt("0.1")) // 0
原始值轉字元
我們使用
String
函數将類型轉換成字元串類型,依然先看 規範15.5.1.1中有關
String
函數的介紹:
如果
String
函數不傳參數,傳回空字元串,如果有參數,調用
ToString(value)
,而
ToString
也給了一個對應的結果表。表如下:
參數類型結果Undefined"undefined"Null"null"Boolean如果參數是 true,傳回 "true"。參數為 false,傳回 "false"Number又是比較複雜,可以看例子String傳回與之相等的值
讓我們寫幾個例子驗證一下:
console.log(String()) // 空字元串
console.log(String(undefined)) // undefined
console.log(String(null)) // null
console.log(String(false)) // false
console.log(String(true)) // true
console.log(String(0)) // 0
console.log(String(-0)) // 0
console.log(String(NaN)) // NaN
console.log(String(Infinity)) // Infinity
console.log(String(-Infinity)) // -Infinity
console.log(String(1)) // 1
注意這裡的
ToString
和上一節的
ToNumber
都是底層規範實作的方法,并沒有直接暴露出來。
原始值轉對象
原始值到對象的轉換非常簡單,原始值通過調用 String()、Number() 或者 Boolean() 構造函數,轉換為它們各自的包裝對象。
null 和 undefined 屬于例外,當将它們用在期望是一個對象的地方都會造成一個類型錯誤 (TypeError) 異常,而不會執行正常的轉換。
var a = 1;
console.log(typeof a); // number
var b = new Number(a);
console.log(typeof b); // object
對象轉布爾值
對象到布爾值的轉換非常簡單:所有對象(包括數組和函數)都轉換為 true。對于包裝對象也是這樣,舉個例子:
console.log(Boolean(new Boolean(false))) // true
對象轉字元串和數字
對象到字元串和對象到數字的轉換都是通過調用待轉換對象的一個方法來完成的。而 JavaScript 對象有兩個不同的方法來執行轉換,一個是
toString
,一個是
valueOf
。注意這個跟上面所說的
ToString
和
ToNumber
是不同的,這兩個方法是真實暴露出來的方法。
所有的對象除了 null 和 undefined 之外的任何值都具有
toString
方法,通常情況下,它和使用 String 方法傳回的結果一緻。
toString
方法的作用在于傳回一個反映這個對象的字元串,然而這才是情況複雜的開始。
在《JavaScript專題之類型判斷(上)》中講到過 Object.prototype.toString 方法會根據這個對象的[[class]]内部屬性,傳回由 "[object " 和 class 和 "]" 三個部分組成的字元串。舉個例子:
Object.prototype.toString.call({a: 1}) // "[object Object]"
({a: 1}).toString() // "[object Object]"
({a: 1}).toString === Object.prototype.toString // true
我們可以看出當調用對象的 toString 方法時,其實調用的是 Object.prototype 上的 toString 方法。
然而 JavaScript 下的很多類根據各自的特點,定義了更多版本的 toString 方法。例如:
- 數組的 toString 方法将每個數組元素轉換成一個字元串,并在元素之間添加逗号後合并成結果字元串。
- 函數的 toString 方法傳回源代碼字元串。
- 日期的 toString 方法傳回一個可讀的日期和時間字元串。
- RegExp 的 toString 方法傳回一個表示正規表達式直接量的字元串。
讀文字太抽象?我們直接寫例子:
console.log(({}).toString()) // [object Object]
console.log([].toString()) // ""
console.log([0].toString()) // 0
console.log([1, 2, 3].toString()) // 1,2,3
console.log((function(){var a = 1;}).toString()) // function (){var a = 1;}
console.log((/d+/g).toString()) // /d+/g
console.log((new Date(2010, 0, 1)).toString()) // Fri Jan 01 2010 00:00:00 GMT+0800 (CST)
而另一個轉換對象的函數是 valueOf,表示對象的原始值。預設的 valueOf 方法傳回這個對象本身,數組、函數、正則簡單的繼承了這個預設方法,也會傳回對象本身。日期是一個例外,它會傳回它的一個内容表示: 1970 年 1 月 1 日以來的毫秒數。
var date = new Date(2017, 4, 21);
console.log(date.valueOf()) // 1495296000000
對象接着轉字元串和數字
了解了 toString 方法和 valueOf 方法,我們分析下從對象到字元串是如何轉換的。看規範 ES5 9.8,其實就是 ToString 方法的對應表,隻是這次我們加上 Object 的轉換規則:
參數類型結果Object1. primValue = ToPrimitive(input, String)
2. 傳回ToString(primValue).
所謂的 ToPrimitive 方法,其實就是輸入一個值,然後傳回一個一定是基本類型的值。
我們總結一下,當我們用 String 方法轉化一個值的時候,如果是基本類型,就參照 “原始值轉字元” 這一節的對應表,如果不是基本類型,我們會将調用一個 ToPrimitive 方法,将其轉為基本類型,然後再參照“原始值轉字元” 這一節的對應表進行轉換。
其實,從對象到數字的轉換也是一樣:
參數類型結果Object1. primValue = ToPrimitive(input, Number)
2. 傳回ToNumber(primValue)。
雖然轉換成基本值都會使用 ToPrimitive 方法,但傳參有不同,最後的處理也有不同,轉字元串調用的是
ToString
,轉數字調用
ToNumber
。
ToPrimitive
那接下來就要看看 ToPrimitive 了,在了解了 toString 和 valueOf 方法後,這個也很簡單。
讓我們看規範 9.1,函數文法表示如下:
ToPrimitive(input[, PreferredType])
第一個參數是 input,表示要處理的輸入值。
第二個參數是 PreferredType,非必填,表示希望轉換成的類型,有兩個值可以選,Number 或者 String。
當不傳入 PreferredType 時,如果 input 是日期類型,相當于傳入 String,否則,都相當于傳入 Number。
如果傳入的 input 是 Undefined、Null、Boolean、Number、String 類型,直接傳回該值。
如果是 ToPrimitive(obj, Number),處理步驟如下:
- 如果 obj 為 基本類型,直接傳回
- 否則,調用 valueOf 方法,如果傳回一個原始值,則 JavaScript 将其傳回。
- 否則,調用 toString 方法,如果傳回一個原始值,則 JavaScript 将其傳回。
- 否則,JavaScript 抛出一個類型錯誤異常。
如果是 ToPrimitive(obj, String),處理步驟如下:
- 如果 obj為 基本類型,直接傳回
- 否則,調用 toString 方法,如果傳回一個原始值,則 JavaScript 将其傳回。
- 否則,調用 valueOf 方法,如果傳回一個原始值,則 JavaScript 将其傳回。
- 否則,JavaScript 抛出一個類型錯誤異常。
對象轉字元串
是以總結下,對象轉字元串(就是 Number() 函數)可以概括為:
- 如果對象具有 toString 方法,則調用這個方法。如果他傳回一個原始值,JavaScript 将這個值轉換為字元串,并傳回這個字元串結果。
- 如果對象沒有 toString 方法,或者這個方法并不傳回一個原始值,那麼 JavaScript 會調用 valueOf 方法。如果存在這個方法,則 JavaScript 調用它。如果傳回值是原始值,JavaScript 将這個值轉換為字元串,并傳回這個字元串的結果。
- 否則,JavaScript 無法從 toString 或者 valueOf 獲得一個原始值,這時它将抛出一個類型錯誤異常。
對象轉數字
對象轉數字的過程中,JavaScript 做了同樣的事情,隻是它會首先嘗試 valueOf 方法
- 如果對象具有 valueOf 方法,且傳回一個原始值,則 JavaScript 将這個原始值轉換為數字并傳回這個數字
- 否則,如果對象具有 toString 方法,且傳回一個原始值,則 JavaScript 将其轉換并傳回。
- 否則,JavaScript 抛出一個類型錯誤異常。
舉個例子:
console.log(Number({})) // NaN
console.log(Number({a : 1})) // NaN
console.log(Number([])) // 0
console.log(Number([0])) // 0
console.log(Number([1, 2, 3])) // NaN
console.log(Number(function(){var a = 1;})) // NaN
console.log(Number(/d+/g)) // NaN
console.log(Number(new Date(2010, 0, 1))) // 1262275200000
console.log(Number(new Error('a'))) // NaN
注意,在這個例子中,
[]
和
[0]
都傳回了 0,而
[1, 2, 3]
卻傳回了一個 NaN。我們分析一下原因:
當我們
Number([])
的時候,先調用
[]
的
valueOf
方法,此時傳回
[]
,因為傳回了一個對象而不是原始值,是以又調用了
toString
方法,此時傳回一個空字元串,接下來調用
ToNumber
這個規範上的方法,參照對應表,轉換為
, 是以最後的結果為
。
而當我們
Number([1, 2, 3])
的時候,先調用
[1, 2, 3]
的
valueOf
方法,此時傳回
[1, 2, 3]
,再調用
toString
方法,此時傳回
1,2,3
,接下來調用
ToNumber
,參照對應表,因為無法轉換為數字,是以最後的結果為
NaN
。
JSON.stringify
值得一提的是:JSON.stringify() 方法可以将一個 JavaScript 值轉換為一個 JSON 字元串,實作上也是調用了 toString 方法,也算是一種類型轉換的方法。下面講一講JSON.stringify 的注意要點:
1.處理基本類型時,與使用toString基本相同,結果都是字元串,除了 undefined
console.log(JSON.stringify(null)) // null
console.log(JSON.stringify(undefined)) // undefined,注意這個undefined不是字元串的undefined
console.log(JSON.stringify(true)) // true
console.log(JSON.stringify(42)) // 42
console.log(JSON.stringify("42")) // "42"
2.布爾值、數字、字元串的包裝對象在序列化過程中會自動轉換成對應的原始值。
JSON.stringify([new Number(1), new String("false"), new Boolean(false)]); // "[1,"false",false]"
3.undefined、任意的函數以及 symbol 值,在序列化過程中會被忽略(出現在非數組對象的屬性值中時)或者被轉換成 null(出現在數組中時)。
JSON.stringify({x: undefined, y: Object, z: Symbol("")});
// "{}"
JSON.stringify([undefined, Object, Symbol("")]);
// "[null,null,null]"
4.JSON.stringify 有第二個參數 replacer,它可以是數組或者函數,用來指定對象序列化過程中哪些屬性應該被處理,哪些應該被排除。
function replacer(key, value) {
if (typeof value === "string") {
return undefined;
}
return value;
}
var foo = {foundation: "Mozilla", model: "box", week: 45, transport: "car", month: 7};
var jsonString = JSON.stringify(foo, replacer);
console.log(jsonString)
// {"week":45,"month":7}
var foo = {foundation: "Mozilla", model: "box", week: 45, transport: "car", month: 7};
console.log(JSON.stringify(foo, ['week', 'month']));
// {"week":45,"month":7}
5.如果一個被序列化的對象擁有 toJSON 方法,那麼該 toJSON 方法就會覆寫該對象預設的序列化行為:不是那個對象被序列化,而是調用 toJSON 方法後的傳回值會被序列化,例如:
var obj = {
foo: 'foo',
toJSON: function () {
return 'bar';
}
};
JSON.stringify(obj); // '"bar"'
JSON.stringify({x: obj}); // '{"x":"bar"}'
深入系列
JavaScript深入系列目錄位址:https://github.com/mqyqingfeng/Blog
JavaScript深入系列預計寫十五篇左右,旨在幫大家捋順JavaScript底層知識,重點講解如原型、作用域、執行上下文、變量對象、this、閉包、按值傳遞、call、apply、bind、new、繼承等難點概念。
如果有錯誤或者不嚴謹的地方,請務必給予指正,十分感謝。如果喜歡或者有所啟發,歡迎 star,對作者也是一種鼓勵。
再其他
現在是校招季,淘系前端正在幫助21屆同學們收割大廠offer,專門建立了交流群,可以在這裡提問題,交流面試經驗,這裡也提供了履歷輔導和答疑解惑等服務,加淘小招微信邀你入群,微信号 taoxiaozhao233