JavaScript深刻之頭疼的類型轉換(上)

在 JavaScript 中,有一部份內容,狀況複雜,容易出錯,飽受爭議但又應用普遍,這即是類型轉換。前端

前言

將值從一種類型轉換爲另外一種類型一般稱爲類型轉換。git

ES6 前,JavaScript 共有六種數據類型:Undefined、Null、Boolean、Number、String、Object。github

咱們先捋一捋基本類型之間的轉換。面試

原始值轉布爾

咱們使用 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。json

原始值轉數字

咱們可使用 Number 函數將類型轉換成數字類型,若是參數沒法被轉換爲數字,則返回 NaN。數組

在看例子以前,咱們先看 ES5 規範 15.7.1.1 中關於 Number 的介紹:閉包

1

根據規範,若是 Number 函數不傳參數,返回 +0,若是有參數,調用 ToNumber(value)app

注意這個 ToNumber 表示的是一個底層規範實現上的方法,並無直接暴露出來。函數

ToNumber 則直接給了一個對應的結果表。表以下:

參數類型 結果
Undefined NaN
Null +0
Boolean 若是參數是 true,返回 1。參數爲 false,返回 +0
Number 返回與之相等的值
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。注意這個跟上面所說的 ToStringToNumber 是不一樣的,這兩個方法是真實暴露出來的方法。

全部的對象除了 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 方法。例如:

  1. 數組的 toString 方法將每一個數組元素轉換成一個字符串,並在元素之間添加逗號後合併成結果字符串。
  2. 函數的 toString 方法返回源代碼字符串。
  3. 日期的 toString 方法返回一個可讀的日期和時間字符串。
  4. 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 的轉換規則:

參數類型 結果
Object 1. primValue = ToPrimitive(input, String)
2. 返回ToString(primValue).

所謂的 ToPrimitive 方法,其實就是輸入一個值,而後返回一個必定是基本類型的值。

咱們總結一下,當咱們用 String 方法轉化一個值的時候,若是是基本類型,就參照 「原始值轉字符」 這一節的對應表,若是不是基本類型,咱們會將調用一個 ToPrimitive 方法,將其轉爲基本類型,而後再參照「原始值轉字符」 這一節的對應表進行轉換。

其實,從對象到數字的轉換也是同樣:

參數類型 結果
Object 1. 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),處理步驟以下:

  1. 若是 obj 爲 基本類型,直接返回
  2. 不然,調用 valueOf 方法,若是返回一個原始值,則 JavaScript 將其返回。
  3. 不然,調用 toString 方法,若是返回一個原始值,則 JavaScript 將其返回。
  4. 不然,JavaScript 拋出一個類型錯誤異常。

若是是 ToPrimitive(obj, String),處理步驟以下:

  1. 若是 obj爲 基本類型,直接返回
  2. 不然,調用 toString 方法,若是返回一個原始值,則 JavaScript 將其返回。
  3. 不然,調用 valueOf 方法,若是返回一個原始值,則 JavaScript 將其返回。
  4. 不然,JavaScript 拋出一個類型錯誤異常。

對象轉字符串

因此總結下,對象轉字符串(就是 Number() 函數)能夠歸納爲:

  1. 若是對象具備 toString 方法,則調用這個方法。若是他返回一個原始值,JavaScript 將這個值轉換爲字符串,並返回這個字符串結果。
  2. 若是對象沒有 toString 方法,或者這個方法並不返回一個原始值,那麼 JavaScript 會調用 valueOf 方法。若是存在這個方法,則 JavaScript 調用它。若是返回值是原始值,JavaScript 將這個值轉換爲字符串,並返回這個字符串的結果。
  3. 不然,JavaScript 沒法從 toString 或者 valueOf 得到一個原始值,這時它將拋出一個類型錯誤異常。

對象轉數字

對象轉數字的過程當中,JavaScript 作了一樣的事情,只是它會首先嚐試 valueOf 方法

  1. 若是對象具備 valueOf 方法,且返回一個原始值,則 JavaScript 將這個原始值轉換爲數字並返回這個數字
  2. 不然,若是對象具備 toString 方法,且返回一個原始值,則 JavaScript 將其轉換並返回。
  3. 不然,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 這個規範上的方法,參照對應表,轉換爲 0, 因此最後的結果爲 0

而當咱們 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深刻系列目錄地址:github.com/mqyqingfeng…

JavaScript深刻系列預計寫十五篇左右,旨在幫你們捋順JavaScript底層知識,重點講解如原型、做用域、執行上下文、變量對象、this、閉包、按值傳遞、call、apply、bind、new、繼承等難點概念。

若是有錯誤或者不嚴謹的地方,請務必給予指正,十分感謝。若是喜歡或者有所啓發,歡迎 star,對做者也是一種鼓勵。

再其餘

創建了一個「前端校招面試衝刺互助羣」,歡迎加「taoxiaozhao233」 入羣~

相關文章
相關標籤/搜索