摘要:我們首先了解一下中有關(guān)類型轉(zhuǎn)換的知識(shí)。新增類型拋出異常從列表可以明顯看到少了一個(gè)類型轉(zhuǎn)換為的規(guī)則。這里要強(qiáng)調(diào)一點(diǎn)第二個(gè)表達(dá)式?jīng)]有涉及到強(qiáng)制類型轉(zhuǎn)換。如果文中有錯(cuò)誤或者有某些強(qiáng)制轉(zhuǎn)換的情形沒(méi)有涉及到請(qǐng)及時(shí)留言告知,我會(huì)修改并補(bǔ)充進(jìn)去。
javascript是一門(mén)非常奇特的語(yǔ)言,它有時(shí)候奇特的會(huì)讓人懷疑人生。比如讓我們看一下下面的一些奇葩例子:
false == "0" //true "哇" false == 0 //true "哦" false == "" //true "噢" false == [] //true "啥?" 0 == "" //true "what?" 0 == [] //true 0 == "0" //true [] == "0" //false "why?" [] == "" //true //-----------更驚訝的是--------------- [] == ![] //true "WTF!" [2] == 2 //true "" == [null] //true 0 == " " //true 我還能說(shuō)什么呢? false == " " //true
還有許多可以列出來(lái)嚇你一跳的例子,別懷疑我是隨便編出來(lái)騙你的。當(dāng)時(shí)我在瀏覽器運(yùn)行這些時(shí),我都懷疑我以前學(xué)得是假的js。如果要形容我當(dāng)時(shí)的表情的話,你想一下黑人小哥的表情就能明白我當(dāng)時(shí)是有多懷疑人生。
好,現(xiàn)在讓我們先喝杯水壓壓驚,暫時(shí)忘記前面那些奇葩的例子。我們首先了解一下js中有關(guān)類型轉(zhuǎn)換的知識(shí)。
學(xué)過(guò)js的應(yīng)該都了解js是一門(mén)弱類型語(yǔ)言。你在聲明一個(gè)變量的時(shí)候沒(méi)有告訴它是什么類型,于是在程序運(yùn)行時(shí),你可能不知不覺(jué)中就更改了變量的類型??赡苡行┦悄愎室飧牡?,另一些可能并不是你的本意,但是不管怎樣你都不可避免的會(huì)遇到類型轉(zhuǎn)換(強(qiáng)制或隱含)。讓我們看一下下面的列子:
var a = "1"; var b= Number(a); // b=1; +a; // 1; b + ""; // "1";
大家應(yīng)該都知道答案,很多人在代碼中或多或少都會(huì)用到這些方法,并且都明白其中發(fā)生了值的類型轉(zhuǎn)換,但是你們是否有深入了解js內(nèi)部在類型轉(zhuǎn)換時(shí)做了哪些操作呢?
ToBoolean(argument)我們首先來(lái)了解強(qiáng)制轉(zhuǎn)換為Boolean類型時(shí),發(fā)生了什么操作。在用調(diào)用Boolean(a)或者!a等操作將值轉(zhuǎn)換為Boolean類型時(shí),js內(nèi)部會(huì)調(diào)用ToBoolean方法來(lái)進(jìn)行轉(zhuǎn)換,該方法定義了以下規(guī)則:
argument的類型 | 轉(zhuǎn)換的結(jié)果 |
---|---|
Undefined | false |
Null | false |
Boolean | argument |
Number | 如果argument是 +0、-0、NaN, 返回false; 否則返回true. |
String | 如果arguments是空字符串(長(zhǎng)度為0)返回false,否則返回true |
Object | true |
Symbol(ES6新增類型) | true |
從這個(gè)列表中我們簡(jiǎn)單概括一下就是只要argument的值是(undefined、null、+0、-0、NaN、""(空字符串)以及false))這7個(gè)里的其中一個(gè),那轉(zhuǎn)換之后返回的是false,其他都為true。js專門(mén)把這7個(gè)值放到一個(gè)falsy列表中,其余值都放在truthy列表。
ToNumber(argument)ToNumber顧名思義即把其它類型轉(zhuǎn)換為Number類型(js內(nèi)部調(diào)用的方法,外部無(wú)法訪問(wèn)到),ECMAScript官方也專門(mén)給出了轉(zhuǎn)換規(guī)則:
argument的類型 | 轉(zhuǎn)換的結(jié)果 |
---|---|
Undefined | NaN |
Null | +0 |
Boolean | false為+0,true為1 |
Number | 返回argument |
Object | 執(zhí)行以下步驟:讓primValue成為T(mén)oPrimitive(argument, hint Number)的返回值,再調(diào)用ToNumber(primValue)返回。 |
Symbol(ES6新增類型) | 拋出TypeError異常. |
從列表可以明顯看到少了一個(gè)String類型轉(zhuǎn)換為Number的規(guī)則。因?yàn)镾tring轉(zhuǎn)Number,js內(nèi)部有非常復(fù)雜的判斷,我這里面不詳細(xì)說(shuō)轉(zhuǎn)換的細(xì)節(jié),有興趣的可以看一ECMAScript官方的說(shuō)明。只要知道它與確定Number字面量值的算法相似,但是要注意一下細(xì)節(jié):
一個(gè)空(empty)的或只包含空格的字符串被轉(zhuǎn)換為+0。
StrWhiteSpace會(huì)轉(zhuǎn)化為+0
StrNumericLiteral前后的StrWhiteSpace會(huì)被忽略。
StrNumericLiteral前面的多個(gè)0會(huì)被忽略。
不是StringNumericLiteral的擴(kuò)展會(huì)變?yōu)镹aN。
在這里特別說(shuō)明一下
StrWhiteSpace:在js中StrWhiteSpace包含WhiteSpace(空白符)和LineTerminator(終止符)。
StrNumericLiteral:可以理解為包含Infinity和數(shù)字的字符串集合。
StringNumericLiteral:包含StrNumericLiteral和StrWhiteSpace的集合
Unicode Code Point | name |
---|---|
U+0009 | 制表符 |
U+000B | 垂直方向的制表符 |
U+000C | 換頁(yè)符 |
U+0020 | 空格符 |
U+00A0 | 不換行空格符 |
U+FEFF | 零寬度不換行空格符 |
其他種類的“Zs”(分隔符,空白) | Unicode “Space_Separator” |
ECMAScript WhiteSpace有意排除具有Unicode“White_Space”屬性但未在類別“Space_Separator”(“Zs”)中分類的所有代碼點(diǎn)。
Zs列表
我這邊列出了Unicode其它“Zs”的列表,感興趣的可以了解一下:
Unicode Code Point | name |
---|---|
U+1680 | OGHAM SPACE MARK |
U+180E | MONGOLIAN VOWEL SEPARATOR |
U+2000 | EN QUAD |
U+2001 | EM QUAD |
U+2002 | EN SPACE |
U+2003 | EM SPACE |
U+2004 | THREE-PER-EM SPACE |
U+2005 | FOUR-PER-EM SPACE |
U+2006 | SIX-PER-EM SPACE |
U+2007 | FIGURE SPACE |
U+2008 | PUNCTUATION SPACE |
U+2009 | THIN SPACE |
U+200A | NARROW NO-BREAK SPACE |
U+202F | FIGURE SPACE |
U+205F | MEDIUM MATHEMATICAL SPACE ? |
U+3000 | IDEOGRAPHIC SPACE |
Unicode Code Point | name |
---|---|
U+000A | 換行符 |
U+000D | 回車 |
U+2028 | 行分隔符 |
U+2029 | 段分隔符 |
上面的過(guò)程說(shuō)的很抽象,不是很容易理解,我們來(lái)看一下具體的列子:
Number(""); //0 empty Number(" "); //0 多個(gè)空格 Number("u0009"); //0 制表符也可以用Number(" ")表示 Number( ); //0 換行符也可以用Number(" ")或Number("u000A")表示 Number("000010"); //10 1前面的多個(gè)0被忽略 Number(" 10 "); //10 string前后多個(gè)StrWhiteSpace Number("u000910u0009"); //10 string前后有制表符 Number("ab"); //NaN
StrNumericLiteral中的其它進(jìn)制的數(shù)字與十進(jìn)制有相似的規(guī)則,但轉(zhuǎn)化的Number值是十進(jìn)制下的值:
Number("0b10"); //2 (二進(jìn)制) Number("0o17"); //15 (八進(jìn)制) Number("0xA"); //10 (十六進(jìn)制)
還有說(shuō)明一點(diǎn)是十進(jìn)制下數(shù)字的科學(xué)計(jì)數(shù)法顯示的字符串也能通過(guò)ToNumber轉(zhuǎn)換為Number類型:
Number("1.2e+21"); //1.2e+21 Number("1.2e-21"); //1.2e-21ToString(argument)
轉(zhuǎn)換為String類型的規(guī)則如下:
argument的類型 | 轉(zhuǎn)換的結(jié)果 |
---|---|
Undefined | "undefined" |
Null | "null" |
Boolean | false為"false",true為true" |
String | argument |
Object | 執(zhí)行以下步驟:讓primValue成為T(mén)oPrimitive(argument, hint String)的返回值,再調(diào)用ToString(primValue)返回。 |
Symbol(ES6新增類型) | 拋出TypeError異常. |
同樣的在表中我也沒(méi)有列出Number類型轉(zhuǎn)換為String類型的規(guī)則,Number轉(zhuǎn)String并不是簡(jiǎn)單的在數(shù)字前后加上‘或“就行了(即使看起來(lái)是這樣),里面涉及到了復(fù)雜的數(shù)學(xué)算法,我不細(xì)說(shuō)(好吧主要是我沒(méi)有特別理解,具體算法可以看文檔),在這里我只列出幾種特殊情況:
假設(shè)Number的值為m:
如果m是NaN,返回String "NaN"。
如果m是+0或-0,返回String "0"。
如果m小于0, 返回字符串連接符"-"和ToString(-m)。
如果m是+∞,返回String "Infinity"。
ToPrimitive(input [ , PreferredType ])我們?cè)谏厦鎀oNumber和ToString方法中注意到Object類型轉(zhuǎn)換為Number和String時(shí)都會(huì)調(diào)用ToPrimitive方法。該方法接受一個(gè)input輸入?yún)?shù)和一個(gè)可選的PreferredType參數(shù)。PreferredType是用來(lái)決定當(dāng)某個(gè)對(duì)象能夠轉(zhuǎn)換為多個(gè)基本類型時(shí)該返回什么類型??墒荰oPrimitive內(nèi)部究竟是如何操作來(lái)返回Number或String類型的呢?如果要深入探究其具體的操作步驟可能花大半天也不能完全理清,里面包含了各種方法的調(diào)用以及復(fù)雜的邏輯判斷還有各種安全檢測(cè),我不仔細(xì)深入下去。我這邊假設(shè)所有的判斷都按正常流程走,所有安全機(jī)制都通過(guò)不報(bào)錯(cuò)誤,那么一個(gè)對(duì)象轉(zhuǎn)換為Number或String就可以概括為以下幾個(gè)判斷:
一個(gè)對(duì)象上是否有@@toPrimitive方法定義,如果有調(diào)用該方法返回結(jié)果。
對(duì)象上如果沒(méi)有定義@@toPrimitive方法,則沿著該對(duì)象的原型鏈向上查找,直到找到或者[[Prototype]]為空。
如果該對(duì)象和其原型鏈上都沒(méi)有定義@@toPrimitive方法,則調(diào)用OrdinaryToPrimitive(O,hint);
hint有PreferredType決定,如果PreferredType是hint Number,hint為"number",PreferredType是hint String,hint為"string",如果沒(méi)定義,默認(rèn)hint為"number",O就是input對(duì)象。
OrdinaryToPrimitive方法的判斷是:如果hint為"string",在O上調(diào)用? "toString", "valueOf" ?。意思是在O以及原型鏈上先查找"toString"方法,找到第一個(gè)toString方法就調(diào)用toString返回結(jié)果,如果沒(méi)有就查找”valueOf“方法來(lái)返回結(jié)果。
如果hint為"number",在O上調(diào)用? "valueOf", "toString" ?。
@@toPrimitive、? "toString", "valueOf" ?和? "valueOf", "toString" ?方法調(diào)用返回一個(gè)Object類型時(shí)可能會(huì)報(bào)TypeError錯(cuò)誤
@@toPrimitive是Symbol類型,是Symbol.toPrimitive的簡(jiǎn)寫(xiě),ES6之前沒(méi)有Symbol類型,所以只需判斷toString和valueOf方法。
我這邊用幾個(gè)例子來(lái)解釋ToPrimitive的運(yùn)行過(guò)程
var a = { [Symbol.toPrimitive]: (hint)=>{ if(hint==="number"){ return 1; }else if(hint==="string"){ return "Symbol.toPrimitive"; }else if(hint==="default"){ return 2; }else{ throw TypeError("不能轉(zhuǎn)換為String和Number之外的類型值"); //防止內(nèi)部出現(xiàn)錯(cuò)誤 } }, toString: () => "toString", valueOf: () => 3 }; Number(a); //1 hint為"number" String(a); //"Symbol.toPrimitive" hint為"string" a + "1"; //"21" a在進(jìn)行+操作符時(shí)hint為"default",因?yàn)槌绦虿恢滥闶亲鲎址嗉舆€是數(shù)值相加 a + 1; //3 +a; //1 此時(shí)hint為"number",為什么hint不是"default",+a實(shí)際上內(nèi)部進(jìn)行ToNumber轉(zhuǎn)換,-、*、/操作符類似 //刪除a中Symbol.toPrimitive屬性 delete a[Symbol.toPrimitive]; Number(a); //3 調(diào)用valueOf方法 String(a); //"toString" 調(diào)用toString方法 a + 1; //4 結(jié)果不是"toString1"是因?yàn)閖s內(nèi)部先判斷valueOf方法 //刪除a中valueOf屬方法 delete a["valueOf"]; Number(a); //NaN 返回的"toString"不能轉(zhuǎn)換為有效數(shù)字 String(a); //"toString" 1 + a; //"1toString" //重寫(xiě)a中的toString方法 a.toString = () = > a; //返回了a對(duì)象 Number(a); //TypeError String(a); //TypeError 1 + a; //TypeError
上面例子看出Object類型在轉(zhuǎn)換為String和Number時(shí)有可能會(huì)出現(xiàn)各種各樣的情況。為此我們最好永遠(yuǎn)不要重寫(xiě)對(duì)象中的valueOf或者toString方法,以防出現(xiàn)意想不到的結(jié)果,如果你重寫(xiě)了方法那么你就要格外小心了。
Object.prototype.toString= () => 1; 1 + {}; //2 看到了嗎?永遠(yuǎn)不要重寫(xiě)Object中的內(nèi)置方法,最好也不要在子對(duì)象中覆蓋Object的內(nèi)置方法。
在此我們對(duì)js中強(qiáng)制轉(zhuǎn)換時(shí)發(fā)生的過(guò)程基本捋了一遍,接下來(lái)我們來(lái)了解一下相等操作符兩邊發(fā)生了什么。
Abstract Equality ComparisonECMAScript官方對(duì)(==)操作的說(shuō)法是Abstract Equality Comparison(抽象的相等比較),它對(duì)x==y定義了下面一些規(guī)則:
如果x和y是同一類型,進(jìn)行Strict Equality Comparison x === y。
如果x是null,y是undefined,返回true。
如果x是undefined,y是null,返回true。
如果x的類型是Number,y的類型是String,進(jìn)行x==ToNumber(y)。
如果x的類型是String,y的類型是Number,進(jìn)行ToNumber(x)==y。
如果x的類型是Boolean,進(jìn)行ToNumber(x)==y。
如果y的類型是Boolean,進(jìn)行x==ToNumber(y)。
如果x的類型是String、Number或者Symbol,y的類型是Object,進(jìn)行x==ToPrimitive(y)。
如果x的類型是Object,y的類型是String、Number或者Symbol,進(jìn)行ToPrimitive(x)==y。
其他返回false
Strict Equality ComparisonStrict Equality Comparison(嚴(yán)格的相等比較)對(duì)x===y定義下列規(guī)則:
如果x和y是不是同一類型, 返回false。
如果x的類型是Number:
- 如果x或y是NaN,返回false。 - 如果x和y數(shù)值相同,返回true。 - 如果x是+0,y是-0,返回true。 - 如果x是-0,y是+0,返回true。 - 其他返回false。
如果x是Undefined類型,返回true。
如果x是Null類型,返回true。
如果x是String類型,x和y是完全相同的代碼單元序列返回true,否則false。
如果x是Boolean類型,x和y都是true或都是false,返回true,否則返回false。
如果x是Symbol類型,x和y是相同的Symbol值,返回true,否則返回false。
如果x和y是相同的對(duì)象,返回true,否則返回false。
驗(yàn)證提到(===)操作符,我們不等不說(shuō)一個(gè)方法Object.is(a,b),該方法也是比較兩個(gè)值是否一樣,但它比(===)更嚴(yán)格。它們之間的區(qū)別在于如果x和y是NaN,返回true。如果x是+0,y是-0,返回false,如果x是-0,y是+0,返回false。
到這里類型轉(zhuǎn)換和相等比較的介紹就告一段落了,現(xiàn)在我們重新回過(guò)頭去看一下最開(kāi)始的幾個(gè)奇特例子,你會(huì)發(fā)現(xiàn)它們之間的關(guān)系比較是如此的正常。我就拿([] == ![])進(jìn)行講解,按照操作符優(yōu)先級(jí)比較,先運(yùn)行![],它的值為false,這時(shí)等式變成([] == false);按(==)的規(guī)則7對(duì)false進(jìn)行ToNumber操作,值變?yōu)?,這時(shí)等式變?yōu)椋╗] == 0);按(==)的規(guī)則9對(duì)[]進(jìn)行ToPrimitive操作,調(diào)用Array上的toString方法,返回"",這時(shí)等式變?yōu)椋?" == 0);按(==)的規(guī)則5對(duì)""進(jìn)行ToNumber操作,值變?yōu)?,這時(shí)等式是(0==0)。我們最終得出結(jié)論([] == ![])是對(duì)的。
補(bǔ)充我們看一下下面的例子:
1 + {}; //"1[object object]" {} + 1; //1 ({} + 1); //"[object object]1"
我們發(fā)現(xiàn)第一和第三個(gè)表達(dá)式按照我們預(yù)期的值輸出了,但是第二個(gè)表達(dá)式卻沒(méi)有。這里要強(qiáng)調(diào)一點(diǎn):第二個(gè)表達(dá)式?jīng)]有涉及到強(qiáng)制類型轉(zhuǎn)換。他把這個(gè)表達(dá)式看成了兩個(gè),一個(gè)是塊{},還有一個(gè)是+1,把{}丟棄l,所以輸出的值1。至于1+{},js把他看成一個(gè)表達(dá)式,所以{}被強(qiáng)制轉(zhuǎn)換為"[object object]";第三個(gè)表達(dá)式加了(),使js認(rèn)為{}+1是一個(gè)整體,所以{}也被強(qiáng)制轉(zhuǎn)換了。
結(jié)束到這里我想說(shuō)的基本就結(jié)束了。如果文中有錯(cuò)誤或者有某些強(qiáng)制轉(zhuǎn)換的情形沒(méi)有涉及到請(qǐng)及時(shí)留言告知,我會(huì)修改并補(bǔ)充進(jìn)去。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/89630.html
摘要:基本概念中有種簡(jiǎn)單數(shù)據(jù)類型也稱為基本數(shù)據(jù)類型,存放在棧中和。在使用聲明變量但未對(duì)其加以初始化時(shí),這個(gè)變量的值就是,例如類型是第二個(gè)只有一個(gè)值的數(shù)據(jù)類型,這個(gè)特殊的值是。類型阿拉伯?dāng)?shù)字的八進(jìn)制十進(jìn)制十六進(jìn)制整數(shù)浮點(diǎn)數(shù)。 基本概念 ECMAScript 中有 5 種簡(jiǎn)單數(shù)據(jù)類型(也稱為基本數(shù)據(jù)類型,存放在棧中):Undefined、Null、Boolean、Number 和String。還...
摘要:基本概念中有種簡(jiǎn)單數(shù)據(jù)類型也稱為基本數(shù)據(jù)類型,存放在棧中和。在使用聲明變量但未對(duì)其加以初始化時(shí),這個(gè)變量的值就是,例如類型是第二個(gè)只有一個(gè)值的數(shù)據(jù)類型,這個(gè)特殊的值是。類型阿拉伯?dāng)?shù)字的八進(jìn)制十進(jìn)制十六進(jìn)制整數(shù)浮點(diǎn)數(shù)。 基本概念 ECMAScript 中有 5 種簡(jiǎn)單數(shù)據(jù)類型(也稱為基本數(shù)據(jù)類型,存放在棧中):Undefined、Null、Boolean、Number 和String。還...
摘要:前言網(wǎng)上其實(shí)已經(jīng)有非常多的學(xué)習(xí)資料了,但是每個(gè)人都有自己的基礎(chǔ),所以往往是有的人講的深一點(diǎn),有的人說(shuō)的淺一點(diǎn)。講述的人們因?yàn)楹ε潞樗脑俅蔚絹?lái),而準(zhǔn)備聯(lián)合起來(lái)修建一座直通天際的高塔以傳揚(yáng)聚集四散的人類。 前言 網(wǎng)上其實(shí)已經(jīng)有非常多的js學(xué)習(xí)資料了,但是每個(gè)人都有自己的基礎(chǔ),所以往往是有的人講的深一點(diǎn),有的人說(shuō)的淺一點(diǎn)。 就我自身而言,想要匹配自己水平的找些資料,往往是十分的零碎,所以可...
摘要:談?wù)勔彩且环N選擇歷史故事在之前是一門(mén)被稱為沒(méi)有塊級(jí)作用域的語(yǔ)言看看代碼輸出結(jié)果權(quán)威解析這是因?yàn)楸宦暶髟诋?dāng)前函數(shù)的作用域內(nèi)不管你聲明在函數(shù)的什么位置在函數(shù)執(zhí)行之前解析器會(huì)掃描當(dāng)前函數(shù)作用域并將以和開(kāi)頭的語(yǔ)句的變量名添加到當(dāng)前函數(shù)作用域內(nèi)這意味 談?wù)?var, let, const. var 也是一種選擇 歷史故事 在 ES6 之前, JavaScript 是一門(mén)被稱為沒(méi)有塊級(jí)作用域的語(yǔ)言...
閱讀 3326·2021-11-18 10:02
閱讀 1546·2021-10-12 10:08
閱讀 1366·2021-10-11 10:58
閱讀 1351·2021-10-11 10:57
閱讀 1253·2021-10-08 10:04
閱讀 2199·2021-09-29 09:35
閱讀 848·2021-09-22 15:44
閱讀 1340·2021-09-03 10:30