這一冊回答這樣兩個問題:面試
類型和值真正的工做方式 強制轉換的角落和縫隙、強制轉換值得花時間學習&合理的地方正則表達式
首先要說明的是,變量沒有類型,變量持有有類型的值。 其中內建類型有:null
、undefined
、string
、number
、boolean
、object
、symbol
(ES6新加); 使用typeof
能夠準確測出除null
外的6個,還有function
:tyepof demo;若是要測試null
:算法
//惟一一個typeof是object可是是fasly的值
var a = null;
(!a && typeof a === "object"); // true
複製代碼
類Array指長得很像Array可是沒有連接到Array.prototype的數據結構,好比DOM查詢操做返回的DOM元素列表(連接到NodeList.prototype)、函數內參數值暴露的arguments
對象(連接到Object.prototype,在ES6被廢棄)。 如何轉換有兩種方式:數組
Array.prototype.slice.call(***)
Array.from(***)
膚淺的類似性體如今它們都有一個length
屬性,一個indexOf
方法和一個concat
方法:demo瀏覽器
說是膚淺說明二者更大的是區別。String是不可變的,String.prototype上掛載的方法操做字符串,不是原地修改內容,而是建立返回一個新的字符串;Array是至關可變的,儘管Array.prototype上掛載的方法有建立返回一個新數組的非變化方法,也有原地修改內容的變化方法。安全
對於String操做,有些Array方法是有用的,咱們能夠對string
"借用"非變化的array
方法:demo;也有著名的反轉字符串的例子是作了String=>Array=>String的轉換:demo。固然若是頻繁借用Array方法,那爲何最開始不直接設爲Array類型。性能優化
0.1+0.2===0.3
爲何是false
?安全整數是什麼?第一個問題,0.1和0.2在js中的二進制形式表達是不精確的,相加結果不是0.3,而是接近0.3的一個值,因此是false
。爲了靠譜地作數值的比較,引入一個容差/機械極小值,在ES6中這個值是Number.EPSILON
,只要差值的絕對值小於容差,認爲是數值相等。bash
第二個問題,JavaScript能表示的最大最小值是Number.MAX_VALUE
和Number.MIN_VALUE
,可是對於整數而言有一個安全範圍,在這個安全範圍內,整數是無誤表示,這個範圍是Number.MIN_SAFE_INTEGER
到Number.MAX_SAFE_INTEGER
。數據結構
NaN
是什麼?如何斷定一個值是NaN
?使用不一樣爲number
的值作操做等到的就是一個NaN
,它是一個哨兵值,能夠理解爲不合法的數字/失敗的數字,試着進行數學操做可是失敗了,這就是失敗的number
結果。函數
NaN
值的斷定不能直接和NaN
比較,由於NaN
是惟一一個本身不等於本身的。ES6給了一個Number.isNaN(..)
,如要要填補的話,類型是數字+本身不等於本身兩個條件。
ES6提供了Object.is(..)
,用來測試兩個值的絕對等價性,沒有任何例外:demo。Object.is(..)
的定位是這樣的:
Object.is(..)
可能不該當用於那些==
或===
已知 安全 的狀況(見第四章「強制轉換」),由於這些操做符可能高效得多,而且更慣用/常見。Object.is(..)
很大程度上是爲這些特殊的等價狀況準備的。
[[Class]]
是什麼?typeof
結果是object
的值被額外地打上一個內部標籤屬性,就是[[Class]]
。這個屬性不能直接訪問,但能夠間件經過Object.prototype.toString.call(..)
訪問。
好比:demo ,注意簡單基本類型string
、number
、boolean
發生封箱行爲;好比結果[Object Array]
後面的Array
反應的是原生類型構造器/內建函數這個信息;
特殊的是null
和undefined
也能工做,結果是[Object Null] [Object Undefined]
,雖然沒有Null
和Undefined
;
Object.prototype.toString.call(..)
也被用來測值類型,比typeof
更完善。
封箱與開箱是兩個相反的過程,封箱將基本類型值包裝進對象,開箱從對象中取出基本類型值。封箱有手動封箱(好比new String("aaa")
)和自動封箱兩種,在基本類型值上訪問對象的屬性和方法就會發生自動封箱,好比var a = "zzz";a.length
。自動封箱,即讓封箱在須要的地方隱含發生,這是推薦的,一個是方便另外一個是瀏覽器對於.length
等常見的狀況有性能優化。開箱也分手動開箱(valueOf
)和自動開箱兩種,當以一種查詢基本類型值的方式使用對象包裝時,自動開箱發生,即開箱隱含發生,給個demo。
Array
、Object
、Function
、RegExp
這四種的話使用字面形式建立值幾乎是更好的選擇。
(使用Array
構造器的一個用途:快速建立undefined * n
的數組)
(使用Object
構造器,得一個一個去添加屬性,麻煩!)
(Function
構造器用於動態定義函數,這篇文章裏有一個demo,可是通常也挺少用的)
使用RegExp
有一個用途是字面形式沒有的——用於建立動態的正則表達式
Date
、Error
這兩種的話沒有字面形式,只能使用原生類型構造器。
使用Date
構造器給一個demo,獲取當前時間時間戳比較經常使用
Error
手動使用比較少,通常是自動被拋出,可是有些像message
、type
、stack
的屬性可能會有用
Symbol
使用Symbol(...)
構造器生成的數據是簡單的基本標量(注意不是object
),Symbol
主要爲特殊的ES6結構的內建行爲設計。能夠自定義,也有預約義的Symbol
值。(這裏簡單帶過了,之後實際遇到了再研究)
ToString
、ToNumber
、ToBoolean
分別是?和強制轉換的關係?強制轉換最後都是轉爲boolean
、string
、number
這樣的基本標量,而抽象操做ToString
、ToNumber
和ToBoolean
規定了這樣的轉換。
首先是ToString
,轉換爲string
,給個demo,注意ToString
能夠被明確地調用,也能夠在一個須要String
的上下文環境中自動地調用;另外想補充一下JSON.stringify(..)
字符串化(自己和ToString
的抽象操做沒有關係,只要是對簡單類型的字符串化行爲比較像),JSON字符串化分安全值和非法值,非法值主要是由於不能移植到其餘消費JSON值的語言,非法值包括undefined
、function
、symbol
和帶有循環引用的object
。object
能夠定義一個toJSON
方法,該方法在JSON.stringify(..)
調用時首先被調用,主要用途是將JSON非法值轉換爲安全值,另外JSON.stringify(..)
也提供第二個參數是替換器——過濾object
屬性、第三個參數填充符。
而後說說ToNumber
,轉換爲number
,給個demo,注意object/array
首先是ToPrimitive
抽象操做。
最後是ToBoolean
,轉換爲boolean
,這個比較簡單,falsy列表中的轉換爲false
:null
、undefined
、""
、+0
、-0
、NaN
、false
。
String
<-->Number
最明確的固然是不帶new
的String(..)
和Number(..)
;另外x.toString()
也被認爲是明確轉爲字符串,儘管背後隱含地發生了封箱;一元操做符+
也被認爲是明確轉爲數字,可是若是狀況是多個+/-
連着,是增長困惑的。
和String-->Number
彷佛有點關聯的是解析數字字符串的行爲(好比parseInt
、parseFloat
),解析數字字符串和類型轉換的一個最大區別是,解析數字字符串是容忍錯誤的,好比parseInt("42px")
結果是"42"
,而類型轉換是零容忍的。關於parseInt
有個網紅面試題。
Boolean
明確地轉爲布爾值類型有兩種方式:不帶new
的Boolean(a)
和兩個否認符號!!a
。Boolean(a)
很是明確,可是不常見,!!a
更加明確。
Numbers --> Strings
兩個操做數的+
操做符是這樣工做的:若是兩個操做數之一是一個string
(或者object/array
經過ToPrimitive抽象操做成爲一個string
),作string
鏈接;其餘狀況作數字加法。使用在隱含地Numbers轉換爲Strings上面是,number + ""
。
Strings --> Numbers
這裏使用-
操做符,-
操做符僅爲數字操做定義,因此兩個操做數都會被轉爲number,即發生ToNumber抽象操做。使用在隱含地Strings轉換爲Numbers上面是,string - 0
。
兩個操做數的+
操做符在沒有string
的狀況是作數字加法,也適用這裏,好比true+true===2
,這裏就隱含地發生了Booleans轉換爲Numbers。這個點有個有意思的用途,好比有3個條件,在僅一個爲true
狀況作後續操做:
const cond1 = ....;
const cond2 = ....;
const cond3 = ....;
if((cond1&&!cond2&&!cond3)||(!cond1&&cond2&&!cond3)||(!cond1&&!cond2&&cond3)){
//後續操做...
}
複製代碼
這裏的boolean邏輯就很複雜,但可以使用前面的方法用數字加法作簡化:
if((!!cond1+!!cond2+!!cond3)===1){//後續操做...}
複製代碼
有些表達式操做會作一個隱含地強制轉換爲boolean
值:
if(..)
for(;;)
第二個子句while(..)
、do{..}while(..)
?:
三元表達式的第一個子句||
、&&
其中||
和&&
被稱爲邏輯或和邏輯與,但也能夠認爲是操做數選擇器操做符,由於:
a || b; //大體至關於 a?a:b
a && b; //大體至關於 a?b:a
複製代碼
另外JS壓縮器會把if(a){foo();}
轉換爲a&&foo()
,由於代碼更短,可是不多有人這麼作(我有這麼作過...)
比較同類型的值時,它們算法是相同的,作的工做也基本是一致的,因此這裏是隨意的;在比較不一樣類型的值,寬鬆等價容許作強制轉換,若是想要強制轉換用寬鬆等價,不然用嚴格等價。 寬鬆不等價與嚴格不等價是在前面兩個的基礎上取反。
同類型的值是簡單天然地比較;
不一樣類型的值:
string
與number
若是Type(x)是Number而Type(y)是String, 返回比較x == ToNumber(y)的結果。
若是Type(x)是String而Type(y)是Number, 返回比較ToNumber(x) == y的結果。
boolean
若是Type(x)是Boolean, 返回比較 ToNumber(x) == y 的結果。
若是Type(y)是Boolean, 返回比較 x == ToNumber(y) 的結果。
這裏掛個demo,提醒千萬不要==true
或者==false
null
與undefined
若是x是null而y是undefined,返回true。
若是x是undefined而y是null,返回true。
null
與undefined
是互相等價的,而不等價於其餘值(惟一寬鬆等價)。這裏給一個僞代碼:
var a = doSomething();
if (a == null) {
// ..
}
複製代碼
不管a
是null
仍是undefined
,檢查都是經過的,而不須要再去作a===undefined||a===null
,更加簡潔。
object
與非object
- 若是Type(x)是一個String或者Number而Type(y)是一個Object, 返回比較 x == ToPrimitive(y) 的結果。
- 若是Type(x)是一個Object而Type(y)是String或者Number, 返回比較 ToPrimitive(x) == y 的結果
寬鬆等價的壞列表指的是比較難理解的比較狀況,發生的也是隱含強制轉換,demo。
爲了不壞列表,給出了能夠遵循的規則:
true
或者false
值,那麼就永遠,永遠不要使用==
。[]
,""
,或0
這些值,那麼認真地考慮不使用==
。在這些狀況,使用===
比==
好
a<b
時發生了什麼?說說抽象關係比較算法大體流程:
a
和b
的值有一個object
類型,首先是ToPrimitive抽象操做處理,使得比較雙方都是基礎類型值string
類型,按照簡單的字典順序比較(數字在字母前面)在這個例子中a<b
、a==b
、a>b
結果都是false
,奇怪的是a>=b
、a<=b
都是true
。由於大於等於、小於等於更貼切的翻譯是不小於、不大於,對比較操做取反。
比較沒有像等價那樣的「嚴格的關係型比較」,因此若是不容許隱含強制轉換髮生,在比較以前作好類型的強制轉換
undefined
和undeclared概念給個代碼片斷就知道這二者的區別:在這裏a就是undefined
,b就是undeclared。
var a;
a; // undefined
b; // ReferenceError: b is not defined
複製代碼
typeof
在對待 undeclared的變量時不會報錯,這是一種安全防衛行爲。它有什麼用呢?
debug.js
文件中有一個DEBUG
的全局變量,只有在開發環境下導入全局,生產環境不會,在其餘的程序代碼中可能會去檢查這個DEBUG
變量:demo第二個實際case沒有遇到過,另外也在連接下面掛出了依賴注入的解決方式。
除了以前提到的使用Date
構造器的兩種方法(new Date().getTime()
和Date.now()
)之外,一元操做符+
也能夠:+new Date
/+new Date()
,可是使用一元操做符+
轉換的方式不夠明確,因此不是很推薦。
~
發生了什麼?有什麼用途?位操做符|
會發生ToInt32
(轉換爲32位整數),像0|x
通常被用於轉換爲32位整數。位操做符~
首先第一步也是ToInt32
的轉換,其次是按位取反,給個demo。用途有兩個:
arr#indexOf
、arr#findIndex
、str#indexOf
時若是找不到結果返回-1
,-1
是一個錯誤狀況的哨兵值,使用>=-1
/==-1
這樣的判斷,是一種「抽象泄露」,而若是使用~x //0當x=-1
這樣的判斷更加優雅~~
截斷數字的小數部分,固然是ToInt32的結果例如分析下面表達式的執行結果:
a && b || c ? c || b ? a : c && b : a
複製代碼
工做分兩步,首先是肯定更緊密的綁定,由於&&
優先級比||
高,而||
優先級比? :
高。因此更緊密的綁定使得上述表達式等價於:
((a && b) || c) ? (c || b) ? a : (c && b) : a
複製代碼
第二步是在同等優先級的操做符狀況下,是從左發生分組(左結合)仍是從右發生分組(右結合),好比?:
是右結合的,因此a?b:c?d:e
是等價於a?b:(c?d:e)
,而不是(a?b:c)?d:e
,因此上述表達式等價於:
((a && b) || c) ? ((c || b) ? a : (c && b)) : a
複製代碼
掘金上有看到問true || true && fn()
表達式中,fn
會不會執行。 按照以前更緊密的綁定分析,等價於表達式true||(true&&fn())
,可是執行順序依然是從左往右,由於||
的短接,因此在第一個選擇數爲true
的狀況,不會去執行第二個選擇數,也就不會執行fn
函數。
let
和const
聲明的變量是在TDZ
中的,因此必須得在聲明以後使用,而不會發生提高;var b = 3;
function foo( a = 42, b = a + b + 5 ) {
// ..
}
複製代碼
在賦值中的b引用將在參數b的TDZ中發生(不會被拉到外面的b引用),因此它會拋出一個錯誤。然而,賦值中的a是沒有問題的,由於那時參數a的TDZ已通過去了。
switch
每一個case是嚴格等價仍是寬鬆等價?答案是嚴格等價,可是寬鬆等價是能夠模擬的:
var a = "42";
switch (true) {
case a == 10:
console.log( "10 or '10'" );
break;
case a == 42:
console.log( "42 or '42'" );
break;
default:
// 永遠不會運行到這裏
}
複製代碼