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