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