前端進階系列(第2期):深入理解JavaScript資料型別轉換

布一發表於2019-01-28

上一期中我們主要是瞭解了JavaScript中存在兩大資料型別:基本型別引用型別以及其儲存的方式(堆和棧)。

本期我們將重點談談JavaScript資料型別轉換過程出現的各種“奇葩”的問題。

寫在前面

在JavaScript中當運算子在運算時,如果兩邊資料不統一,CPU就無法計算,這時我們編譯器會自動將運算子兩邊的資料做一個資料型別轉換,轉成一樣的資料型別再計算,這種無需程式設計師手動轉換,而由編譯器自動轉換的方式就稱為隱式轉換

在JavaScript中“一切皆是物件”,在我們具體瞭解隱式轉換前先了解一下物件的兩個方法:toString()valueOf()

toString()

toString() 方法返回一個表示該物件的字串。

// 數字轉字串(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()方法的返回值和返回值型別均可能不同。

// 數字的原始值(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(基本型別)

// 字串 + 數字console.log('hello' + 123) // 'hello' + '123'// 字串 + 布林console.log('hello' + true) // 'hello' + 'true'// 字串 + nullconsole.log('hello' + null) // 'hello' + 'null'// 字串 + undefinedconsole.log('hello' + undefined) // 'hello' + 'undefined'複製程式碼

數字 VS 加號連線符

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

// 數字 + 布林console.log(1 + true) // 2// 等同於console.log(1 + Number(true)) // 1 + 1// 數字 + undefinedconsole.log(1 + undefined) // NaN// 等同於console.log(1 + Number(undefined)) // 1 + NaN// 數字 + nullconsole.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 不等於 NaNconsole.log(NaN == NaN) // false// undefined vs nullconsole.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]'複製程式碼

案例一:

目的:驗證非自定義物件的隱式轉換過程

// 申明一個物件obj1var 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(),來驗證物件的隱式轉換過程

// 申明一個物件obj2var 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()) // stringconsole.log(obj2.valueOf()) // 18console.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) // objectconsole.log(null instanceof Object) // falseconsole.log(NaN == NaN) // falseconsole.log(null == undefined) // trueconsole.log(Number(null)) // 0console.log(Number(undefined)) // NaNconsole.log(0 == '') // trueconsole.log(0 == '     ') // trueconsole.log('' != ' ') //trueconsole.log(null != 0) // trueconsole.log(undefined != 0) // true複製程式碼

寫在最後

通過上面對JavaScript中的資料型別的隱式轉換可以總結出以下結論:

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

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

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

交流

更多精彩內容請關注我的github部落格,如果你覺得還不錯請給個star,非常感謝。

來源:https://juejin.im/post/5c4edc1ef265da6144206911

相關文章