JavaScript是一種動態類型語言,變量是沒有類型的,可以隨時賦予任意值。但是,數據本身和各種運算是有類型的,因此運算時變量需要轉換類型。大多數情況下,這種數據類型轉換是自動的,但是有時也需要手動強制轉換。
[TOC]
## 強制轉換
強制轉換主要指使用Number、String和Boolean三個構造函數,手動將各種類型的值,轉換成數字、字符串或者布爾值。
### Number函數:強制轉換成數值
使用Number函數,可以將任意類型的值轉化成數字。
(1)原始類型值的轉換規則
* 數值:轉換后還是原來的值。
* 字符串:如果可以被解析為數值,則轉換為相應的數值,否則得到NaN。空字符串轉為0。
* 布爾值:true轉成1,false轉成0。
* undefined:轉成NaN。
* null:轉成0。
~~~
Number("324") // 324
Number("324abc") // NaN
Number("") // 0
Number(false) // 0
Number(undefined) // NaN
Number(null) // 0
~~~
Number函數將字符串轉為數值,要比parseInt函數嚴格很多。基本上,只要有一個字符無法轉成數值,整個字符串就會被轉為NaN。
~~~
parseInt('011') // 9
parseInt('42 cats') // 42
parseInt('0xcafebabe') // 3405691582
Number('011') // 11
Number('42 cats') // NaN
Number('0xcafebabe') // 3405691582
~~~
上面代碼比較了Number函數和parseInt函數,區別主要在于parseInt逐個解析字符,而Number函數整體轉換字符串的類型。另外,Number會忽略八進制的前導0,而parseInt不會。
Number函數會自動過濾一個字符串前導和后綴的空格。
~~~
Number('\t\v\r12.34\n ')
~~~
(2)對象的轉換規則
對象的轉換規則比較復雜。
1. 先調用對象自身的valueOf方法,如果該方法返回原始類型的值(數值、字符串和布爾值),則直接對該值使用Number方法,不再進行后續步驟。
2. 如果valueOf方法返回復合類型的值,再調用對象自身的toString方法,如果toString方法返回原始類型的值,則對該值使用Number方法,不再進行后續步驟。
3. 如果toString方法返回的是復合類型的值,則報錯。
~~~
Number({a:1})
// NaN
~~~
上面代碼等同于
~~~
if (typeof {a:1}.valueOf() === 'object'){
Number({a:1}.toString());
} else {
Number({a:1}.valueOf());
}
~~~
上面代碼的valueOf方法返回對象本身({a:1}),所以對toString方法的返回值“[object Object]”使用Number方法,得到NaN。
如果toString方法返回的不是原始類型的值,結果就會報錯。
~~~
var obj = {
valueOf: function () {
console.log("valueOf");
return {};
},
toString: function () {
console.log("toString");
return {};
}
};
Number(obj)
// TypeError: Cannot convert object to primitive value
~~~
上面代碼的valueOf和toString方法,返回的都是對象,所以轉成數值時會報錯。
從上面的例子可以看出,valueOf和toString方法,都是可以自定義的。
~~~
Number({valueOf:function (){return 2;}})
// 2
Number({toString:function(){return 3;}})
// 3
Number({valueOf:function (){return 2;},toString:function(){return 3;}})
// 2
~~~
上面代碼對三個對象使用Number方法。第一個對象返回valueOf方法的值,第二個對象返回toString方法的值,第三個對象表示valueOf方法先于toString方法執行。
### String函數:強制轉換成字符串
使用String函數,可以將任意類型的值轉化成字符串。規則如下:
(1)原始類型值的轉換規則
* 數值:轉為相應的字符串。
* 字符串:轉換后還是原來的值。
* 布爾值:true轉為“true”,false轉為“false”。
* undefined:轉為“undefined”。
* null:轉為“null”。
~~~
String(123) // "123"
String("abc") // "abc"
String(true) // "true"
String(undefined) // "undefined"
String(null) // "null"
~~~
(2)對象的轉換規則
如果要將對象轉為字符串,則是采用以下步驟。
1. 先調用toString方法,如果toString方法返回的是原始類型的值,則對該值使用String方法,不再進行以下步驟。
2. 如果toString方法返回的是復合類型的值,再調用valueOf方法,如果valueOf方法返回的是原始類型的值,則對該值使用String方法,不再進行以下步驟。
3. 如果valueOf方法返回的是復合類型的值,則報錯。
String方法的這種過程正好與Number方法相反。
~~~
String({a:1})
// "[object Object]"
~~~
上面代碼相當于下面這樣。
~~~
String({a:1}.toString())
// "[object Object]"
~~~
如果toString方法和valueOf方法,返回的都不是原始類型的值,則String方法報錯。
~~~
var obj = {
valueOf: function () {
console.log("valueOf");
return {};
},
toString: function () {
console.log("toString");
return {};
}
};
String(obj)
// TypeError: Cannot convert object to primitive value
~~~
下面是一個自定義toString方法的例子。
~~~
String({toString:function(){return 3;}})
// "3"
String({valueOf:function (){return 2;}})
// "[object Object]"
String({valueOf:function (){return 2;},toString:function(){return 3;}})
// "3"
~~~
上面代碼對三個對象使用String方法。第一個對象返回toString方法的值(數值3),然后對其使用String方法,得到字符串“3”;第二個對象返回的還是toString方法的值("[object Object]"),這次直接就是字符串;第三個對象表示toString方法先于valueOf方法執行。
### Boolean函數:強制轉換成布爾值
使用Boolean函數,可以將任意類型的變量轉為布爾值。
(1)原始類型值的轉換方法
以下六個值的轉化結果為false,其他的值全部為true。
* undefined
* null
* -0
* +0
* NaN
* ''(空字符串)
~~~
Boolean(undefined) // false
Boolean(null) // false
Boolean(0) // false
Boolean(NaN) // false
Boolean('') // false
~~~
(2)對象的轉換規則
所有對象的布爾值都是true,甚至連false對應的布爾對象也是true。
~~~
Boolean(new Boolean(false))
// true
~~~
請注意,空對象{}和空數組[]也會被轉成true。
~~~
Boolean([]) // true
Boolean({}) // true
~~~
## 自動轉換
當遇到以下幾種情況,JavaScript會自動轉換數據類型:
* 不同類型的數據進行互相運算;
* 對非布爾值類型的數據求布爾值;
* 對非數值類型的數據使用一元運算符(即“+”和“-”)。
### 自動轉換為布爾值
當JavaScript遇到預期為布爾值的地方(比如if語句的條件部分),就會將非布爾值的參數自動轉換為布爾值。它的轉換規則與上面的“強制轉換成布爾值”的規則相同,也就是說,在預期為布爾值的地方,系統內部會自動調用Boolean方法。
因此除了以下六個值,其他都是自動轉為true:
* undefined
* null
* -0
* +0
* NaN
* ''(空字符串)
~~~
if (!undefined && !null && !0 && !NaN && !''){
console.log('true');
}
// true
~~~
### 自動轉換為字符串
當JavaScript遇到預期為字符串的地方,就會將非字符串的數據自動轉為字符串,轉換規則與“強制轉換為字符串”相同。
字符串的自動轉換,主要發生在加法運算時。當一個值為字符串,另一個值為非字符串,則后者轉為字符串。
~~~
'5' + 1 // '51'
'5' + true // "5true"
'5' + false // "5false"
'5' + {} // "5[object Object]"
'5' + [] // "5"
'5' + function (){} // "5function (){}"
'5' + undefined // "5undefined"
'5' + null // "5null"
~~~
### 自動轉換為數值
當JavaScript遇到預期為數值的地方,就會將參數值自動轉換為數值,轉換規則與“強制轉換為數值”相同。
除了加法運算符有可能把運算子轉為字符串,其他運算符都會把兩側的運算子自動轉成數值。
~~~
'5' - '2' // 3
'5' * '2' // 10
true - 1 // 0
false - 1 // -1
'1' - 1 // 0
'5'*[] // 0
false/'5' // 0
'abc'-1 // NaN
~~~
上面都是二元算術運算符的例子,JavaScript的兩個一元算術運算符——正號和負號——也會把運算子自動轉為數值。
~~~
+'abc' // NaN
-'abc' // NaN
+true // 1
-false // 0
~~~
### 小結
由于自動轉換有很大的不確定性,而且不易除錯,建議在預期為布爾值、數值、字符串的地方,全部使用Boolean、Number和String方法進行顯式轉換。
## 加法運算符的類型轉化
加法運算符(+)需要特別討論,因為它可以完成兩種運算(加法和字符連接),所以不僅涉及到數據類型的轉換,還涉及到確定運算類型。
### 三種情況
加法運算符的類型轉換,可以分成三種情況討論。
(1)運算子之中存在字符串
兩個運算子之中,只要有一個是字符串,則另一個不管是什么類型,都會被自動轉為字符串,然后執行字符串連接運算。前面的《自動轉換為字符串》一節,已經舉了很多例子。
(2)兩個運算子都為數值或布爾值
這種情況下,執行加法運算,布爾值轉為數值(true為1,false為0)。
~~~
true + 5 // 6
true + true // 2
~~~
(3)運算子之中存在對象
運算子之中存在對象(或者準確地說,存在非原始類型的值),則先調用該對象的valueOf方法。如果返回結果為原始類型的值,則運用上面兩條規則;否則繼續調用該對象的toString方法,對其返回值運用上面兩條規則。
~~~
1 + [1,2]
// "11,2"
~~~
上面代碼的運行順序是,先調用[1,2].valueOf(),結果還是數組[1,2]本身,則繼續調用[1,2].toString(),結果字符串“1,2”,所以最終結果為字符串“11,2”。
~~~
1 + {a:1}
// "1[object Object]"
~~~
對象{a:1}的valueOf方法,返回的就是這個對象的本身,因此接著對它調用toString方法。({a:1}).toString()默認返回字符串"[object Object]",所以最終結果就是字符串“1[object Object]”
有趣的是,如果更換上面代碼的運算次序,就會得到不同的值。
~~~
{a:1} + 1
// 1
~~~
原來此時,JavaScript引擎不將{a:1}視為對象,而是視為一個代碼塊,這個代碼塊沒有返回值,所以被忽略。因此上面的代碼,實際上等同于 {a:1};+1 ,所以最終結果就是1。為了避免這種情況,需要對{a:1}加上括號。
~~~
({a:1})+1
"[object Object]1"
~~~
將{a:1}放置在括號之中,由于JavaScript引擎預期括號之中是一個值,所以不把它當作代碼塊處理,而是當作對象處理,所以最終結果為“[object Object]1”。
~~~
1 + {valueOf:function(){return 2;}}
// 3
~~~
上面代碼的valueOf方法返回數值2,所以最終結果為3。
~~~
1 + {valueOf:function(){return {};}}
// "1[object Object]"
~~~
上面代碼的valueOf方法返回一個空對象,則繼續調用toString方法,所以最終結果是“1[object Object]”。
~~~
1 + {valueOf:function(){return {};}, toString:function(){return 2;}}
// 3
~~~
上面代碼的toString方法返回數值2(不是字符串),則最終結果就是數值3。
~~~
1 + {valueOf:function(){return {};}, toString:function(){return {};}}
// TypeError: Cannot convert object to primitive value
~~~
上面代碼的toString方法返回一個空對象,JavaScript就會報錯,表示無法獲得原始類型的值。
### 四個特殊表達式
有了上面這些例子,我們再進一步來看四個特殊表達式。
(1)空數組 + 空數組
~~~
[] + []
// ""
~~~
首先,對空數組調用valueOf方法,返回的是數組本身;因此再對空數組調用toString方法,生成空字符串;所以,最終結果就是空字符串。
(2)空數組 + 空對象
~~~
[] + {}
// "[object Object]"
~~~
這等同于空字符串與字符串“[object Object]”相加。因此,結果就是“[object Object]”。
(3)空對象 + 空數組
~~~
{} + []
// 0
~~~
JavaScript引擎將空對象視為一個空的代碼塊,加以忽略。因此,整個表達式就變成“+ []”,等于對空數組求正值,因此結果就是0。轉化過程如下:
~~~
+ []
// Number([])
// Number([].toString())
// Number("")
// 0
~~~
如果JavaScript不把前面的空對象視為代碼塊,則結果為字符串“[object Object]”。
~~~
({}) + []
// "[object Object]"
~~~
(4)空對象 + 空對象
~~~
{} + {}
// NaN
~~~
JavaScript同樣將第一個空對象視為一個空代碼塊,整個表達式就變成“+ {}”。這時,后一個空對象的ValueOf方法得到本身,再調用toSting方法,得到字符串“[object Object]”,然后再將這個字符串轉成數值,得到NaN。所以,最后的結果就是NaN。轉化過程如下:
~~~
+ {}
// Number({})
// Number({}.toString())
// Number("[object Object]")
~~~
如果,第一個空對象不被JavaScript視為空代碼塊,就會得到“[object Object][object Object]”的結果。
~~~
({}) + {}
// "[object Object][object Object]"
({} + {})
// "[object Object][object Object]"
console.log({} + {})
// "[object Object][object Object]"
var a = {} + {};
a
// "[object Object][object Object]"
~~~
需要指出的是,對于第三和第四種情況,Node.js的運行結果不同于瀏覽器環境。
~~~
{} + {}
// "[object Object][object Object]"
{} + []
// "[object Object]"
~~~
可以看到,Node.js沒有把第一個空對象視為代碼塊。原因是Node.js的命令行環境,內部執行機制大概是下面的樣子:
~~~
eval.call(this,"(function(){return {} + {}}).call(this)")
~~~
Node.js把命令行輸入都放在eval中執行,所以不會把起首的大括號理解為空代碼塊加以忽略。
## 參考鏈接
* Axel Rauschmayer,?[What is {} + {} in JavaScript?](http://www.2ality.com/2012/01/object-plus-object.html)
* Axel Rauschmayer,?[JavaScript quirk 1: implicit conversion of values](http://www.2ality.com/2013/04/quirk-implicit-conversion.html)
* Benjie Gillam,?[Quantum JavaScript?](http://www.benjiegillam.com/2013/06/quantum-javascript/)
- 第一章 導論
- 1.1 前言
- 1.2 為什么學習JavaScript?
- 1.3 JavaScript的歷史
- 第二章 基本語法
- 2.1 語法概述
- 2.2 數值
- 2.3 字符串
- 2.4 對象
- 2.5 數組
- 2.6 函數
- 2.7 運算符
- 2.8 數據類型轉換
- 2.9 錯誤處理機制
- 2.10 JavaScript 編程風格
- 第三章 標準庫
- 3.1 Object對象
- 3.2 Array 對象
- 3.3 包裝對象和Boolean對象
- 3.4 Number對象
- 3.5 String對象
- 3.6 Math對象
- 3.7 Date對象
- 3.8 RegExp對象
- 3.9 JSON對象
- 3.10 ArrayBuffer:類型化數組
- 第四章 面向對象編程
- 4.1 概述
- 4.2 封裝
- 4.3 繼承
- 4.4 模塊化編程
- 第五章 DOM
- 5.1 Node節點
- 5.2 document節點
- 5.3 Element對象
- 5.4 Text節點和DocumentFragment節點
- 5.5 Event對象
- 5.6 CSS操作
- 5.7 Mutation Observer
- 第六章 瀏覽器對象
- 6.1 瀏覽器的JavaScript引擎
- 6.2 定時器
- 6.3 window對象
- 6.4 history對象
- 6.5 Ajax
- 6.6 同域限制和window.postMessage方法
- 6.7 Web Storage:瀏覽器端數據儲存機制
- 6.8 IndexedDB:瀏覽器端數據庫
- 6.9 Web Notifications API
- 6.10 Performance API
- 6.11 移動設備API
- 第七章 HTML網頁的API
- 7.1 HTML網頁元素
- 7.2 Canvas API
- 7.3 SVG 圖像
- 7.4 表單
- 7.5 文件和二進制數據的操作
- 7.6 Web Worker
- 7.7 SSE:服務器發送事件
- 7.8 Page Visibility API
- 7.9 Fullscreen API:全屏操作
- 7.10 Web Speech
- 7.11 requestAnimationFrame
- 7.12 WebSocket
- 7.13 WebRTC
- 7.14 Web Components
- 第八章 開發工具
- 8.1 console對象
- 8.2 PhantomJS
- 8.3 Bower:客戶端庫管理工具
- 8.4 Grunt:任務自動管理工具
- 8.5 Gulp:任務自動管理工具
- 8.6 Browserify:瀏覽器加載Node.js模塊
- 8.7 RequireJS和AMD規范
- 8.8 Source Map
- 8.9 JavaScript 程序測試
- 第九章 JavaScript高級語法
- 9.1 Promise對象
- 9.2 有限狀態機
- 9.3 MVC框架與Backbone.js
- 9.4 嚴格模式
- 9.5 ECMAScript 6 介紹
- 附錄
- 10.1 JavaScript API列表
- 草稿一:函數庫
- 11.1 Underscore.js
- 11.2 Modernizr
- 11.3 Datejs
- 11.4 D3.js
- 11.5 設計模式
- 11.6 排序算法
- 草稿二:jQuery
- 12.1 jQuery概述
- 12.2 jQuery工具方法
- 12.3 jQuery插件開發
- 12.4 jQuery.Deferred對象
- 12.5 如何做到 jQuery-free?
- 草稿三:Node.js
- 13.1 Node.js 概述
- 13.2 CommonJS規范
- 13.3 package.json文件
- 13.4 npm模塊管理器
- 13.5 fs 模塊
- 13.6 Path模塊
- 13.7 process對象
- 13.8 Buffer對象
- 13.9 Events模塊
- 13.10 stream接口
- 13.11 Child Process模塊
- 13.12 Http模塊
- 13.13 assert 模塊
- 13.14 Cluster模塊
- 13.15 os模塊
- 13.16 Net模塊和DNS模塊
- 13.17 Express框架
- 13.18 Koa 框架