摘要:其實這三個函數不僅僅可以當作構造函數,它們可以直接當作普通的函數來使用,將任何類型的參數轉化成原始類型的值其實這三個函數用于類型轉換的時候,調用的就是內部的方法這里解釋一下的過程執行執行內部函數執行因為不是原始類型,進入下一步。
本文修改自本人以前寫的文章。
從類型說起js只有7種類型:
原始類型(primitives types)
boolean
number
包括Infinity和NaN,你可以通過typeof Infinity;來驗證
string
null
undefined
Symbol (ECMAScript 6 新定義,暫時用不上,這篇文章不討論)
Object 類型
js內置了很多對象供你使用,MDN文檔列舉了所有ES標準定義的內置對象。
js的自動裝箱雖然string是原始類型,但為什么我們好像可以調用“string的函數”呢?
var str = "I am str"; str.toUpperCase(); // "I AM STR"
原因是js標準庫給boolean、number、string分別提供了包裝類型:Boolean、Number、String。在需要的時候,原始類型會自動轉換成相應的包裝對象(這個過程叫自動裝箱)。上例的toUpperCase就是String標準對象定義的一個函數。
自動裝箱就是臨時創建一個包裝對象,將原始類型的值封裝起來,以便調用包裝對象的函數。但是原來那個變量的值不會有任何變化!執行完上面例子的代碼之后,str指向的依然是那個原始值:
typeof str; // "string"
當然,你可以將Boolean 、Number 、String 這三個函數當作構造函數來使用,通過手動new包裝類來裝箱(得到包裝對象):
var str_object = new String("I am str_object"); // 手動裝箱 str_object.toUpperCase(); // "I AM STR_OBJECT" typeof str_object; // "object"
這三個函數除了能當作構造函數,還能作為普通函數來調用(得到對應的原始類型值)。在文章的后面,我們會將這三個函數當作普通的函數使用,實現強制類型轉換。兩個與類型轉換有關的函數:valueOf()和toString()
valueOf()的語義是,返回這個對象邏輯上對應的原始類型的值。比如說,String包裝對象的valueOf(),應該返回這個對象所包裝的字符串。
toString()的語義是,返回這個對象的字符串表示。用一個字符串來描述這個對象的內容。
valueOf()和toString()是定義在Object.prototype上的方法,也就是說,所有的對象都會繼承到這兩個方法。但是在Object.prototype上定義的這兩個方法往往不能滿足我們的需求(Object.prototype.valueOf()僅僅返回對象本身),因此js的許多內置對象都重寫了這兩個函數,以實現更適合自身的功能需要(比如說,String.prototype.valueOf就覆蓋了在Object.prototype中定義的valueOf)。當我們自定義對象的時候,最好也重寫這個方法。重寫這個方法時要遵循上面所說的語義。
以下是部分內置對象調用valueOf()的行為:
對象 | 返回值 |
---|---|
Array | 數組本身(對象類型)。 |
Boolean | 布爾值(原始類型)。 |
Date | 從 UTC 1970 年 1 月 1 日午夜開始計算,到所封裝的日期所經過的毫秒數(原始類型)。 |
Function | 函數本身(對象類型)。 |
Number | 數字值(原始類型)。 |
Object | 對象本身(對象類型)。如果自定義對象沒有重寫valueOf方法,就會使用它。 |
String | 字符串值(原始類型)。 |
由上表可見,valueOf()雖然期望返回原始類型的值,但是實際上有一些對象在邏輯上無法找到與之對應的原始值,因此只能返回對象本身。
toString()則不一樣,因為不管什么對象,我們總有辦法“描述”它,因此js內置對象的toString()總能返回一個原始string類型的值。
var d = new Date(); d.toString() // "Fri Apr 21 2017 14:54:04 GMT+0800 (中國標準時間)"
我們自己在重寫toString()的時候也應該返回合理的string。
valueOf()和toString()經常會在類型轉換的時候被js內部調用,比如說我們后文會談到的ToPrimitive。在自定義對象上合理地覆蓋valueOf()和toString(),可以控制自定義對象的類型轉換。js內部用于實現類型轉換的4個函數
這4個方法實際上是ECMAScript定義的4個抽象的操作,它們在js內部使用,進行類型轉換。我們js的使用者不能直接調用這些函數,但是了解這些函數有利于我們理解js類型轉換的原理。
ToPrimitive ( input [ , PreferredType ] )
ToBoolean ( argument )
ToNumber ( argument )
ToString ( argument )
請區分這里的ToString()和上文談到的toString(),一個是js引擎內部使用的函數,另一個是定義在對象上的函數。ToPrimitive ( input [ , PreferredType ] )
將input轉化成一個原始類型的值。PreferredType參數要么不傳入,要么是Number 或 String。如果PreferredType參數是Number,ToPrimitive這樣執行:
如果input本身就是原始類型,直接返回input。
調用input.valueOf(),如果結果是原始類型,則返回這個結果。
調用input.toString(),如果結果是原始類型,則返回這個結果。
拋出TypeError異常。
以下是PreferredType不為Number時的執行順序。
如果PreferredType參數是String,則交換上面這個過程的第2和第3步的順序,其他執行過程相同。
如果PreferredType參數沒有傳入
如果input是內置的Date類型,PreferredType 視為String
否則PreferredType 視為 Number
可以看出,ToPrimitive依賴于valueOf和toString的實現。ToBoolean ( argument )
Argument Type | Result |
---|---|
Undefined | Return false |
Null | Return false |
Boolean | Return argument |
Number | 僅當argument為 +0, -0, or NaN時, return false; 否則一律 return true |
String | 僅當argument是空字符串(長度為0)時, return false; 否則一律 return true |
Symbol | Return true |
Object | Return true |
表格來自ECMAScript標準。
只需要記憶0, null, undefined, NaN, ""返回false就可以了,其他一律返回true。
Argument Type | Result |
---|---|
Undefined | Return NaN |
Null | Return +0 |
Boolean | 如果 argument 為 true, return 1. 如果 argument 為 false, return +0 |
Number | 直接返回argument |
String | 將字符串中的內容轉化為數字(比如"23"->23),如果轉化失敗則返回NaN(比如"23a"->NaN) |
Symbol | 拋出 TypeError 異常 |
Object | 先primValue = ToPrimitive(argument, Number),再對primValue 使用 ToNumber(primValue) |
由上表可見ToNumber的轉化并不總是成功,有時會轉化成NaN,有時則直接拋出異常。
ToString ( argument )Argument Type | Result |
---|---|
Undefined | Return "undefined" |
Null | Return "null" |
Boolean | 如果 argument 為 true, return "true".如果 argument 為 false, return "false" |
Number | 用字符串來表示這個數字 |
String | 直接返回 argument |
Symbol | 拋出 TypeError 異常 |
Object | 先primValue = ToPrimitive(argument, hint String),再對primValue使用ToString(primValue) |
當js期望得到某種類型的值,而實際在那里的值是其他的類型,就會發生隱式類型轉換。系統內部會自動調用我們前面說ToBoolean ( argument )、ToNumber ( argument )、ToString ( argument ),嘗試轉換成期望的數據類型。
例子1:
if ( !undefined && !null && !0 && !NaN && !"" ) { console.log("true"); } // true
例子1:因為在if的括號中,js期望得到boolean的值,所以對括號中每一個值都使用ToBoolean ( argument ),將它們轉化成boolean。
例子2:
3 * { valueOf: function () { return 5 } }; //15
例子2:因為在乘號的兩端,js期望得到number類型的值,所以對右邊的那個對象使用ToNumber ( argument ),得到結果5,再與乘號左邊的3相乘。
例子3:
> function returnObject() { return {} } > 3 * { valueOf: function () { return {} }, toString: function () { return {} } } // TypeError: Cannot convert object to primitive value
例子3:調用ToNumber ( argument )的過程中,調用了ToPrimitive ( input , Number ),因為在ToPrimitive中valueOf和toString都沒有返回原始類型,所以拋出異常。
符號"+"是一個比較棘手的一個符號,因為它既可以表示“算數加法”,也可以表示“字符串拼接”。
簡單理解版本:只要"+"兩端的任意一個操作數是字符串,那么這個"+"就表示字符串拼接,否則表示算數加法。
12+3 // 15 12+"3" // "123"
原理理解版本:根據ECMAScript的定義,對"+"運算的求值按照以下過程:
令lval = 符號左邊的值,rval = 符號右邊的值
令lprim = ToPrimitive(lval),rprim = ToPrimitive(rval)
如果lprim和rprim中有任意一個為string類型,將ToString(lprim)和ToString(rprim)的結果做字符串拼接
否則,將ToNumber(lprim)和ToNumber(rprim)的結果做算數加法
根據這個原理可以解釋
[]+[] // "" // 提示:ToPrimitive([])返回空字符串 [] + {} // "[object Object]" // 提示:ToPrimitive({})返回"[object Object]" 123 + { toString: function () { return "def" } } // "123def" // 提示:ToPrimitive(加號右邊的對象)返回"def" {} + [] // 0 // 結果不符合我們的預期:"[object Object]" // 提示:在Chrome中,符號左邊的{}被解釋成了一個語句塊,而不是一個對象 // 注意在別的執行引擎上可能會將{}解釋成對象 // 這一行等價于"+[]" // "+anyValue"等價于Number(anyValue) ({}) + [] // "[object Object]" // 加上括號以后,{}被解釋成了一個對象,結果符合我們的預期了
"<"、">"的情況與"+"類似,但是處理方式與"+"有些不同。如果好奇請自行查閱文檔。顯式類型轉換(強制類型轉換)
程序員顯式調用Boolean(value)、Number(value)、String(value)完成的類型轉換,叫做顯示類型轉換。
我們在文章的前面說過new Boolean(value)、new Number(value)、new String(value)傳入各自對應的原始類型的值,可以實現“裝箱”——將原始類型封裝成一個對象。其實這三個函數不僅僅可以當作構造函數,它們可以直接當作普通的函數來使用,將任何類型的參數轉化成原始類型的值:
Boolean("sdfsd"); // true Number("23"); // 23 String({a:24}); // "[object Object]"
其實這三個函數用于類型轉換的時候,調用的就是js內部的ToBoolean ( argument )、ToNumber ( argument )、ToString ( argument )方法!
這里解釋一下String({a:24}); // "[object Object]"的過程:
執行String({a:24})
執行js內部函數ToString ( {a:24} )
執行primValue = ToPrimitive({a:24}, hint String)
因為{a:24}不是原始類型,進入下一步。
在ToPrimitive內調用({a:24}).toString(),返回了原始值"[object Object]",因此直接返回這個字符串,ToPrimitive后面的步驟不用進行下去了。
primValue被賦值為ToPrimitive的返回值:"[object Object]"
執行js內部函數ToString ( "[object Object]" ),返回"[object Object]"
返回"[object Object]"
返回"[object Object]"
返回"[object Object]"
為了防止出現意料之外的結果,最好在不確定的地方使用顯式類型轉換。
參考文章:
ECMAScript類型轉換規范
Object to primitive conversion
What is {} + {} in JavaScript?
JavaScript quirk 1: implicit conversion of values
Object.prototype.toString()的原理 - ECMAScript
改變Object.prototype.toString.call(myClass)的輸出
比較操作符的類型轉換
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/97376.html
摘要:當一個值為字符串,另一個值為非字符串,則后者轉為字符串。文章出自的個人博客 showImg(https://segmentfault.com/img/bVEWkS?w=3376&h=1312); JavaScript 是一門弱類型語言,剛接觸的時候感覺方便快捷(不需要聲明變量類型了耶!),接觸久了會發現它帶來的麻煩有的時候不在預期之內 呵呵一笑,哪有這么夸張,可能有人看過這樣一段代碼 ...
摘要:下面是用實現轉成抽象語法樹如下還支持繼承以下是轉換結果最終的結果還是代碼,其中包含庫中的一些函數。可以使用新的易于使用的類定義,但是它仍然會創建構造函數和分配原型。 這是專門探索 JavaScript 及其所構建的組件的系列文章的第 15 篇。 想閱讀更多優質文章請猛戳GitHub博客,一年百來篇優質文章等著你! 如果你錯過了前面的章節,可以在這里找到它們: JavaScript 是...
摘要:使用新的易用的類定義,歸根結底也是要創建構造函數和修改原型。首先,它把構造函數當成單獨的函數且包含類屬性集。該節點還儲存了指向父類的指針引用,該父類也并儲存了構造函數,屬性集和及父類引用,依次類推。 原文請查閱這里,略有刪減,本文采用知識共享署名 4.0 國際許可協議共享,BY Troland。 本系列持續更新中,Github 地址請查閱這里。 這是 JavaScript 工作原理的第...
摘要:使用新的易用的類定義,歸根結底也是要創建構造函數和修改原型。首先,它把構造函數當成單獨的函數且包含類屬性集。該節點還儲存了指向父類的指針引用,該父類也并儲存了構造函數,屬性集和及父類引用,依次類推。 原文請查閱這里,略有刪減,本文采用知識共享署名 4.0 國際許可協議共享,BY Troland。 本系列持續更新中,Github 地址請查閱這里。 這是 JavaScript 工作原理的第...
摘要:本文將會深入分析的引擎的內部實現。該引擎使用在谷歌瀏覽器內部。同其他現代引擎如或所做的一樣,通過實現即時編譯器在執行時將代碼編譯成機器代碼。這可使正常執行期間只發生相當短的暫停。 原文 How JavaScript works: inside the V8 engine + 5 tips on how to write optimized code 幾周前我們開始了一個系列博文旨在深入...
閱讀 1404·2021-11-22 15:11
閱讀 2843·2019-08-30 14:16
閱讀 2761·2019-08-29 15:21
閱讀 2920·2019-08-29 15:11
閱讀 2461·2019-08-29 13:19
閱讀 2992·2019-08-29 12:25
閱讀 423·2019-08-29 12:21
閱讀 2838·2019-08-29 11:03