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