引言javascript
惡補我慘不忍睹的js基礎,從毫無頭緒到有一點頭緒,畫個圈但願能逐漸找到適合個人方法。目前在跟着犀牛書一章一章的過,把難點和看不懂的地方拎出來網上多找些文章看一看,相關的面試題目作一作。java
第一篇先從永遠很混亂,永遠覺得清楚了但其實仍是不清楚的隱式類型轉換開始,本篇對標犀牛書第6版3.6包裝對象,3.8 類型變換 ,第4章表達式和運算符。面試
既然要作類型轉換,首先要了解js中有6大原始類型 (ES10中新增第7種bigInt 目前絕大部分生產環境仍是ES6, 暫且忽略),包括Bool
String
Number
Null
Undefined
Symbol
(ES6新增的)和引用類型Object
,包括Array
,Function
,Regexp
,Date
等,這篇文章中暫沒考慮Symbol類型。數組
原始類型的判斷可使用typeof, 例如 typeof 'aabb' === 'string'
引用類型的判斷可使用instanceof, 例如 [] instanceof Array === true
bash
注意如下兩個特殊狀況post
typeof null === 'object';
typeof function(){} === 'function'
複製代碼
已知js數據類型有兩大類,原始類型和引用類型,則類型轉換無非原始類型之間的轉換、原始類型轉引用類型、引用類型轉原始類型。ui
犀牛書中已經列出了各類類型轉換對應的規則: this
原始類型的轉換很簡單,對應表格就行,注意紅框中幾種看似簡單可是容易混淆的轉換:spa
引用類型和原始類型的互換就是常說的裝箱和拆箱的概念,裝箱即把原始類型轉換爲對應的引用類型,拆箱即把引用類型轉換爲原始類型。prototype
裝箱常常發生在後面所說的常見場景中的屬性訪問表達式,好比說:
var o = {x: 1, y: 2};
var arr = [1, 2, 3];
o.x //訪問對象o的x屬性
arr[0] //訪問數組arr的索引爲0的元素
複製代碼
若是.
或[
以前的表達式不是對象或數組,js會將其轉換爲對象,這也是爲何雖然原始類型不能擴展屬性和方法但咱們仍然能直接使用相似str.substring()
調用方法。這個過程當中實際發生瞭如下幾個步驟:
str
經過調用new String(str)的方法建立一個實例substring
方法同字符串同樣,數字和布爾值也有對應的方法:Number()
、Bool()
, null和undefined則沒有包裝對象,訪問它們的屬性會拋出一個類型錯誤。
引用類型到原始值的轉換則比較複雜,犀牛書說的有點繞且多是因爲翻譯的緣由表達上略有歧義,使我理解上走了很多彎路,總的來講符合如下規則:
對象轉換爲字符串:
對象轉換爲數字:
對象轉換爲布爾值: 恆爲真值
或許你已經注意到對象轉數字規則中的,」若是須要的話「這個註釋,先不用管它,假設js是永遠須要的,後面會說到是在什麼狀況下不須要。
既然明確了轉化類型目標之後的規則已經明確了,那麼js在作隱式轉換時都是從自身類型到那種類型呢? 這纔是本文的重點,也是我常常迷惑的問題。
我整理了幾種js發生隱式轉化的常見場景,以後如有發現再作補充:
第2種場景很簡單,若是if條件語句中只有單個變量的時候,會將變量轉換爲bool值,若是是表達式則先計算表達式結果再轉換爲bool值,第三種場景則是上文提到的裝箱,也沒必要再說,接下來只具體介紹一下第一種場景。
基本的算術運算符包括二元算術運算符+、-、*、/、%
和一元算術運算符+、-、++、--
,二元加法運算符比較複雜,隨後單獨講,其他的幾種都很簡單,即將操做數轉化爲數字,全部沒法轉化爲數字的都轉化爲NaN,且操做結果也是NaN, 這裏注意一下一元+
運算符便可,平時用的不多,但一些複雜的面試題中可能會出現,只要把操做數轉化爲數字便可。
這裏有兩個額外的tips:
- 求餘操做符的結果的符號和第一個操做數保持一致,eg:
5%2 == 1
5%-2 == 1
-5%2 == -1
- 一元自增自減運算符要求操做數爲一個左值,左值並不是是在左邊的值而是指變量、對象屬性或數組元素,不然會拋出一個錯誤
![]()
+
運算符二元加法運算符的複雜之處在於既能對兩個數字作加法,也能作字符串鏈接,行爲表現爲:
若是其中一個操做數是對象,則遵循上面提到的對象轉爲原始值規則轉換爲原始值,其中又遵循兩點規則:
若是其中一個操做數是字符串的話,另外一個操做數也會轉化爲字符串而後進行字符串拼接;
不然兩個操做數都轉化爲數字(或NaN),而後作加法
關係運算符包括==、===、!=、!==、>、<、>=、<=
等,首先要知道嚴格相等運算符===
和不嚴格相等運算符!==
是不會作類型轉換的,咱們只看列出來的其他幾種關係運算符。若是兩個操做數的類型不一樣,這幾種關係表達式的行爲表現以下:
null == null
、undefined == undefined
、null == undefined
,除此以外,undefined
和null
與其餘任何結果的比較值都爲false;
NaN
和其餘任何類型比較永遠返回false
(包括和他本身),NaN == NaN //false
若是一個操做數是字符串,一個操做數是數字,先將字符串轉換爲數字;
若是其中一個操做數是bool
值,先將其轉換爲數字;
若是一個操做數是對象,另外一個是數字或字符串,則遵循上面提到的對象轉爲原始值規則轉換爲原始值再進行比較,這裏的轉換和二元+
運算符同樣,符合:
其餘不一樣類型之間的比較均不相等
總結:關係表達式優先把操做數轉化爲數字,包括對象,只是對象轉化爲數字時直接使用返回的結果並不強制轉化爲數字,Date
對象除外;
這裏也有幾個額外的tips:
- 0 == -0 //true
- 因此大寫的ASCII字母都小於小寫的ASCII字母,因此在進行字符串比較的時候注意作大小寫轉換,eg:
'Zore' > 'area' // false
- null === undefined //false 但 null == undefined //true
<=
和>=
運算符只是簡單的不大於和不小於,它們在判斷相等的時候並不依賴==
或===
運算符的比較規則- 字符串相等比較的特殊狀況
![]()
其餘表達式包括邏輯表達式、複製表達式和一些未提到的運算符等,在隱式類型轉換中咱們不用太關心,由於他們的轉換會比較明確,其中咱們只須要知道:
&&
,邏輯或||
是會在特定狀況下短路的,即只計算左邊的表達式不計算右邊的表達式;以注意一些具備反作用的運算的考察便可,eg: var i = 1; false && i++;
這裏的i++表達式並無被計算,除此以外,邏輯非運算符!
會將操做數的布爾值求反,能夠用兩次邏輯非運算!!
來獲得一個操做數的布爾值。
js的類型轉換的表現知足如下幾點規則:
null == null
、undefined == undefined
、null == undefined
,除此以外,undefined
和null
與其餘任何結果的比較值都爲false
;NaN
和其餘任何類型比較永遠返回false
(包括和他本身),NaN == NaN //false
+、==、!=
和關係運算中,若是其中一個操做數是對象,執行對象到原始值轉換valueOf
再調用toString
;toString
再調用valueOf
;這篇總結就到這裏了,對我來講已經比較明晰了,但願對大家來講也有所幫助。
先看幾道題目,能夠先作完再看後面的解析(部分來源於參考文章):
1. [1,2] + 1
2. [1,2] - 1
3. [1,2] + [3,4]
4. true + false
5. "number" + 15 + 3
6. 15 + 3 + "number"
7. "foo" + + "bar"
8. "true" == true
9. false == "false"
10. !!"false" == !!"true"
11. ["x"] == "x"
12. [] + null + 1
13. [1,2,3] == [1,2,3]
14. [] + {}
15. {} + []
16. {} + [] + {} + [1]
17. ! + [] + [] + ![]
18. 全部的假值包括哪些?
19. 將一個變量強制轉換爲字符串,你能說幾種方法?它們有什麼優缺點?
20. 什麼樣的處理可讓`a == 1 && a == 2 && a == 3`表達式爲`true`?
複製代碼
解析:
」1,21「
根據加法運算規則,對象轉換爲原始類型,[1,2]
先調用valueOf()
獲得它自己,仍是一個引用類型因而繼續調用toString()
獲得」1,2「
直接使用,再根據規則字符串與數字相加,數字轉換爲字符串作字符串相連
NaN
根據減法運算規則,操做數直接轉化爲數字,對象轉數字且將toString()
的返回值強制轉化爲數字即NaN
,NaN - 1
仍是NaN
"1,23,4"
對象轉原始類型且toString()
的返回值直接使用,字符串拼接獲得」1,23,4「
1
沒有引用類型,沒有字符串,操做數轉數字相加
」number153「
表達式從左向右執行
」18number「
考察點同上
」fooNaN「
表達式會解析成」foo「 + ( + "bar" )
,根據一元+
運算符,操做數轉數字獲得NaN
,」foo「 + NaN
將NaN
轉字符串作字符串拼接,注意加號之間的空格,若是無空格就是自增運算符了
false
==
優先轉數字,其中一個操做符是布爾值,先轉爲數字獲得」true「 == 1
,其中一個操做數是字符串,另外一個是數字,先將字符串轉換爲數字獲得 NaN == 1
即false
false
考點同上
true
邏輯非運算符的優先級比關係運算符的優先級高,兩個操做數會先被轉換爲布爾值即true == true
,顯然表達式結果爲true
true
其中一個操做數是對象,按規則轉化爲原始值,且toString()
的返回值直接使用"x" == "x"
顯然成立
」null1「
表達式從左往右執行,[] + null
,對象轉原始值獲得」「
,連續作字符串拼接獲得」null1「
false
引用類型比較的是內存地址
"[object Object]"
14,15題放一塊兒看,[] + {}`` []
先轉原始值爲」「
,{}
轉原始值爲"[object Object]"
0
這裏有些意外,這涉及到JavaScript的語法解析規則,在這段代碼中,解析器遇到{}後將其解析爲了一個空的代碼塊,直接忽略,因而表達式被解析爲+ []
,根據一元操做符+
,[]
轉數字爲0
」0[object Object]1「
是14,15的考點結合的升級版
"truefalse"
根據運算符的優先級表達式解析爲!(+[]) + [] + (![])
,即!0 + [] + false
即true + [] + false
即true + "" + false
全部的假值包括
undefined
null
NaN
0
-0
""
false
//還有一種特殊的假值對象
document.all; //輸出當前文檔下的全部標籤
Object.prototype.toString.call(document.all); //[object HTMLAllCollection]
typeof document.all; //undefined
Boolean(document.all); //false,意外吧?!!!
複製代碼
假設變量爲str,將變量強制轉換爲字符串有如下幾種方法:
new String(str)
toString
if(str.toString()) str.toString()
else Object.prototype.toString.call(str)
複製代碼
str + ""
JSON.stringfy(str)
第一種最爲穩妥,第二種若是對象重寫了toString
方法則會影響最終的結果,第三種方法,根據對象轉原始值的規則,是先嚐試調用valueOf
再調用toString()
,若是對象重寫了自身的valueOf
方法或toString()
方法則會影響到最終的結果,第四種方法,若是傳入的值自己就是字符串會獲得雙重引號的值,eg: JSON.stringify("11")
獲得」「11」「
,另外若是對象內有遞歸引用,還會拋出報錯,eg:
var a = {},b = {};
a.param = b;
b.param = a;
JSON.stringify(a);
//VM14994:4 Uncaught TypeError: Converting circular structure to JSON
複製代碼
除此以外
[MDN](developer.mozilla.org/zh-CN/docs/…
還給出瞭如下提醒:
根據對象轉換爲原始值的規則,能夠這樣寫:
var a = {
value: [3, 2, 1],
valueOf: function() { return this.value },
toString: function() {return this.value.pop()}
}
複製代碼