前端進階系列(第2期):深刻理解JavaScript數據類型轉換

上一期中咱們主要是瞭解了JavaScript中存在兩大數據類型:基本類型引用類型以及其存儲的方式(堆和棧)。javascript

本期咱們將重點談談JavaScript數據類型轉換過程出現的各類「奇葩」的問題。java

寫在前面

在JavaScript中當運算符在運算時,若是兩邊數據不統一,CPU就沒法計算,這時咱們編譯器會自動將運算符兩邊的數據作一個數據類型轉換,轉成同樣的數據類型再計算,這種無需程序員手動轉換,而由編譯器自動轉換的方式就稱爲隱式轉換git

在JavaScript中「一切皆是對象」,在咱們具體瞭解隱式轉換前先了解一下對象的兩個方法:toString()valueOf()程序員

toString()

toString() 方法返回一個表示該對象的字符串。github

// 數字轉字符串
(123).toString() // '123'
// 布爾值轉字符串
(true).toString() // 'true'
// 數組轉字符串
['hello', 'world'].toString() // 'hello,world'
// 對象轉字符串
({name: 'hello world'}).toString() // '[object Object]'
//日期對象轉字符串
Date().toString() // 'Wed Jan 23 2019 21:10:42 GMT+0800 (China Standard Time)'
//JSON對象轉字符串
JSON.toString() // '[object JSON]'
// Function轉字符串
Function.toString() // 'function Function() { [native code] }'
// 函數轉字符串
(function(){ return 1; }).toString() // 'function () { return 1; }'
複製代碼

valueOf()

valueOf() 方法返回指定對象的原始值。數組

JavaScript調用valueOf方法將對象轉換爲原始值。你不多須要本身調用valueOf方法;當遇到要預期的原始值的對象時,JavaScript會自動調用它。函數

默認狀況下,valueOf方法由Object後面的每一個對象繼承。 每一個內置的核心對象都會覆蓋此方法以返回適當的值。若是對象沒有原始值,則valueOf將返回對象自己。大數據

JavaScript的許多內置對象都重寫了該函數,以實現更適合自身的功能須要。所以,不一樣類型對象的valueOf()方法的返回值和返回值類型都可能不一樣。ui

// 數字的原始值
(123).valueOf() // 123
// 布爾值的原始值
(true).valueOf() // true
// 數組的原始值
['hello', 'world'].valueOf() // [ 'hello', 'world' ]
// 對象的原始值
({name: 'hello world'}).valueOf() // { name: 'hello world' }
//日期對象的原始值
Date().valueOf() // 'Wed Jan 23 2019 21:10:42 GMT+0800 (China Standard Time)'
//JSON的原始值
JSON.valueOf() // 'Object [JSON] {}'
// Function的原始值
Function.valueOf() // '[Function: Function]'
// 函數的原始值
(function func(){ return 1; }).valueOf() // '[Function: func]'
複製代碼

隱式轉換規則

  1. 轉成string類型:+(字符串鏈接符)
  2. 轉成number類型:++/--(自增或自減運算符)、+ - * / % (算術運算符)、> < >= <= == != === !== (關係運算符)
  3. 轉成boolean類型:!(邏輯非運算符)

字符串 VS 加號鏈接符

字符串 + 基本類型 = 字符串 + String(基本類型)this

// 字符串 + 數字
console.log('hello' + 123) // 'hello' + '123'
// 字符串 + 布爾
console.log('hello' + true) // 'hello' + 'true'
// 字符串 + null
console.log('hello' + null) // 'hello' + 'null'
// 字符串 + undefined
console.log('hello' + undefined) // 'hello' + 'undefined'
複製代碼

數字 VS 加號鏈接符

數字 + 基本類型(非字符串) = 數字類型 + Number(基本類型)

// 數字 + 布爾
console.log(1 + true) // 2
// 等同於
console.log(1 + Number(true)) // 1 + 1
// 數字 + undefined
console.log(1 + undefined) // NaN
// 等同於
console.log(1 + Number(undefined)) // 1 + NaN
// 數字 + null
console.log(1 + null) // 1
// 等同於
console.log(1 + Number(null)) // 1 + 0
複製代碼

數字 + 引用類型 = 數字 + 引用類型.toString()

// 數字 + 數組
console.log(1 + []) // '1'
// 等同於
console.log(1 + [].toString()) // 1 + '' = '1'
// 數字 + 對象
console.log(1 + {}) // '1[object Object]'
// 等同於
console.log(1 + ({}).toString()) // 1 + '[object Object]'
複製代碼

數字類型 + 函數 = 數字類型 + String(函數)

// 數字 + 函數
var func = function() { var a = 2; return 2; }
console.log(1 + func); // 1function () {var a = 2; return 2;}
複製代碼

關係運算符的隱式轉換

規則:將其餘數據類型轉換成數字類型以後再比較關係

// 字符串 vs 數字 = Number(字符串) vs 數字
console.log('2' > 10); // flase
// 等同於
console.log(Number('2') > 10) // 2 > 10
// 字符串(數字) vs 字符串(數字) = ASCII碼(對應值) vs ASCII碼(對應值)
console.log('2' > '10'); // true
// 等同於
console.log('2'.charCodeAt() > '10'.charCodeAt) // 50 > 49
// 字符串(字母) vs 字符串(字母) = ASCII碼(對應值) vs ASCII碼(對應值)
console.log('abc' > 'b'); // false
// 等同於
console.log('abc'.charCodeAt() > 'b'.charCodeAt()) // 97 > 98
// NaN 不等於 NaN
console.log(NaN == NaN) // false
// undefined vs null
console.log(undefined == null) // true
//注意:undefined == null 不等同於
console.log(Number(undefined) == Number(null)) // false NaN == 0
// 這裏JavaScript的特殊約定的結果,undefined == null,詳情能夠查看更多資料
// https://codeburst.io/javascript-null-vs-undefined-20f955215a2
複製代碼

邏輯非與關係運算符的隱式轉換

// 數字 vs 數組 = 數字 vs Number(數組)
console.log([] == 0); // true
// 等同於
console.log(Number([]) == 0) // 0 == 0
// 數字 vs 布爾 = 數字 vs Number(布爾)
console.log(![] == 0); //true
// 等同於
console.log(Number(![]) == 0) // Number(false) == 0 
// 引用類型 != 引用類型
console.log([] == []); // false
// 邏輯非隱式轉換
console.log([] == ![]); // true
// 等同於
console.log(Number([]) == Number(!Boolean([]))) // 0 == 0
// 邏輯非隱式轉換
console.log({} == !{}); // false
// 等同於
console.log(Number({}) == Number(!Boolean({}))) // NaN == 0 
複製代碼

引用類型的隱式轉換

規則:

  1. 當引用類型的valueOf()調用時返回的值是一個基本類型時,則直接進行運算。
  2. 當引用類型的valueOf()調用時返回的值不是一個基本類型時,則引用類型的toString()將會被調用並返回轉換後的字符串,而後再進行運算。
// 字符串 + 數組
console.log('hello' + []) // 'hello' + [].toString()
// 等同於
console.log('hello' + [].toString()) // 'hello' + ''
// 字符串 + 對象
console.log('hello' + {}) // 'hello[object Object]'
// 等同於
console.log('hello' + ({}).toString()) // 'hello' + '[object Object]'
複製代碼

案例一:

目的:驗證非自定義對象的隱式轉換過程

// 申明一個對象obj1
var obj1 = { age: 18 }
console.log(10 + obj1) // '10[object Object]'
複製代碼

第一步:判斷對象的valueOf()返回值是不是基本類型

console.log(obj1.valueOf()) // { age: 18 }
console.log(typeof obj1.valueOf()) // object 返回的是一個對象
複製代碼

第二步:調用對象的toString()返回一個表示該對象的字符串

console.log(obj1.toString()) // '[object Object]'
複製代碼

第三步:根據運算規則進行運算(非字符鏈接操做都轉換成Number進行運算)

// 由於obj1.toString() 返回的是字符串,因此進行字符串鏈接操做
console.log(10 + obj1.toString()) // 10 + '[object Object]'
複製代碼

案例二:

目的:經過自定義對象的valueOf()和toString(),來驗證對象的隱式轉換過程

// 申明一個對象obj2
var obj2 = { 
    age: 18
    toString: function() {
        return '' + this.age;   
    },
    valueOf: function() {
        return this.age;
    }
}
console.log(10 + obj2) // 10 + 18 = 28
複製代碼

第一步:判斷對象的valueOf()返回值是不是基本類型

console.log(obj2.toString()) // '18'
console.log(typeof obj2.toString()) // string
console.log(obj2.valueOf()) // 18
console.log(typeof obj2.valueOf()) // number
複製代碼

第二步:若是第一步能正確返回基本類型,則直接跳到第三步,不然將調用對象的toString()返回一個表示該對象的字符串

// 若是對象的valueOf()返回的是基本類型
console.log(10 + obj2) // 10 + obj2.valueOf()
// 若是對象的valueOf()返回的是引用類型
console.log(10 + obj2) // 10 + obj2.toString()
複製代碼

第三步:根據運算規則進行運算(非字符鏈接操做都轉換成Number進行運算)

// 若是obj2的返回值是字符串,都進行字符串 VS 加號規則
console.log(10 + '18') // 10 + String(obj2)
// 若是obj2的返回值是非字符串,都進行數字 VS 加號規則
console.log(10 + obj2) // 10 + Number(obj2)
複製代碼

特殊說明

JavaScript中存在幾個特殊的原始值:null、undefined、''、0、NaN。

console.log(typeof null) // object
console.log(null instanceof Object) // false
console.log(NaN == NaN) // false
console.log(null == undefined) // true
console.log(Number(null)) // 0
console.log(Number(undefined)) // NaN
console.log(0 == '') // true
console.log(0 == ' ') // true
console.log('' != ' ') //true
console.log(null != 0) // true
console.log(undefined != 0) // true
複製代碼

寫在最後

經過上面對JavaScript中的數據類型的隱式轉換能夠總結出如下結論:

  1. JavaScript中運算符在運算時,最終都將轉換成相同類型進行運算(字符串類型、數字類型)
  2. 字符串與加號鏈接符運算時轉換成String類型
  3. 非字符串加號鏈接符的運算都將轉換成Number類型
  4. 特別注意引用類型的隱式轉換是先判斷valueOf()返回的類型,若是返回是引用類型則調用toString()返回對應的字符串值,最終都是按照1,2,3的規則進行運算。

以上內容雖然有進行驗證,但不知道描述上是否存在歧義,有些點表述的不是很清楚,望諒解。

若是有發現任何問題或者有更好的建議,歡迎直接給我留言。

交流

更多精彩內容請關注個人github博客,若是你以爲還不錯請給個star,很是感謝。

相關文章
相關標籤/搜索