天天都在寫的JS判斷語句,你真的瞭解嗎?

在真實的世界裏,人與人老是有那麼一點不信任,「爭是非,辨明理」是常事。在編程開發領域中,一樣須要對變量的存在、類型、真僞進行校驗,幾乎天天都是在和if === typeof 打交道。可是你真的弄懂了這些判斷語句嗎?編程

1、真假判斷

「真亦假時假亦真,無爲有處有還無。」——《紅樓夢》後端

if( x )

在if語句中,會觸發Boolean上下文,JavaScript會將值強制類型轉換爲布爾值,再執行判斷。 當 x is Trusy時,if語句命中。markdown

在JavaScript中,存在七種假值(又稱虛值),分別爲undefined null "" false 0 0n NaN。其中0nESFuture中新增的一種假值。函數

undefined是最多見的假值,當判斷在一個對象中是否存在某個字段時,使用的就是undefined空值判斷:工具

let people = {name}
if(people.age){  // 等價於 people.age === void 0
		// ...
}
複製代碼

建議在代碼中使用void 0來代替undefined,緣由是undefined不是JS的保留字,並且void 0undefined字符少。 事實上,很多JavaScript壓縮工具在壓縮過程當中,將undefined使用void 0代替。oop

2、相等判斷

雄兔腳撲朔,雌兔眼迷離。雙兔傍地走,安能辨我是雄雌。——《木蘭詩》spa

在JavaScript中,有兩個相等運算符來判斷兩個操做數是否相等,一個是==相等運算符,另外一個是===全等運算符。它們最大區別在於對類型的寬容度。prototype

x === y

===全等運算符對左右兩邊的孩子(操做數)是嚴厲的,就像一個嚴厲的父親。 ===全等運算符首先會檢查兩邊的操做數類型是否一致,而後再檢查其值。具體流程以下:code

  1. 兩邊類型不一樣,返回false;orm

  2. 類型相同,比較其值:

    a. 雙方都是number類型:

    有一方是`NaN`,返回 false;
     一方是+0,一方是-0,返回 true;
     雙方值相同,返回 true;
     其餘,返回 false;
    複製代碼

    b. 雙方都是string類型:

    對雙方挨個比較字符,若字符順序數量相同,返回 true;
     其餘,返回 false;
    複製代碼

    c. 雙方均是boolean類型:

    值相同,返回 true;
     其餘,返回 false;
    複製代碼

    d. 雙方均是object類型:

    若引用地址相同,返回 true;
     其餘,返回 false;
    複製代碼

x == y

==相等運算符對左右兩邊的孩子(操做數)是寬容的,就像一個慈祥的母親。 當對兩個操做數進行比較時,JavaScript會先對其中一個操做數隱式類型轉換,那麼當兩個操做數進行比較時,都作了些什麼?咱們須要從ECMA標準中尋找答案。

基本流程以下:

  1. 若是type(x) === type(y) , 此處比較過程與===相同;
  2. 若是x is nully is undefined,則返回true;反之亦然;
  3. 若是x is stringy is number,則執行ToNumber(x) == y;反之亦然;
  4. 若是x is boolean,則執行ToNumber(x) == y;反之亦然;
  5. 若是x is object,則執行ToPrimitive(x) == y;反之亦然;
  6. 都不是以上狀況,返回false;

簡要概述:類型相同,拼家世拼內涵;類型不一樣,先平等後比較;nullundefined是一家,number一家獨大,其餘都要向我靠;object你別驕傲,降成平民再比較。

在上面過程當中咱們看到,若是兩個操做數的類型相同,則比較過程與===是一致的。若是類型不一樣,那麼就須要隱式類型轉換爲類型相同的狀況後再比較。

上面提到了ToNumberToPrimitive的轉換過程就是隱式的類型轉換。那麼它們究竟作了些什麼呢?

ToNumber(x):

1. x is undefined, return NaN;
2. x is null, return 0;
3. x is number, return x;
4. x is boolean, return x === true ? 1 : 0;
5. x is string, return [ToNumber Applied to the String Type](http://www.ecma-international.org/ecma-262/5.1/#sec-9.3.1);
6. x is object, return ToNumber(ToPrimitive(x))
複製代碼

ToPrimitive(x):

1. x is object, return x.valueOf() or x.toString();
2. x is non-object, return x;
複製代碼

在ECMA標準中,ToPrimitive(x)能夠指定第二個參數PreferredType = "number" | "string",區別在於當x is object時,先調用valueOf()方法仍是先調用toString()方法,默認"number"。 至此,咱們大體知道了隱式類型轉換都作了哪些「幕後工做」了。

通過以上分析,我得出的結論是:能不用==就不要用它,隱式類型轉換規則較多,容易產生意想不到的結果。

固然,==也不是一無可取的,那麼什麼時候用==呢? 當接口返回的值多是數字,多是字符串,而你又不能肯定時,好比obj.chance == 1; 然而,我仍然鼓勵你積極和你的後端小夥伴溝通一下,明確下發字段的類型比較穩妥呢!

Object.is(x, y)

在ES6,新增了一種判斷兩個操做數是否相等的方法,也是最爲嚴格的判等方式——Object.is()。使用它,能夠確保兩個操做必定是相等的,容不得一點沙子。

Object.is()===全等運算符的區別在於對待NaN+0-0的斷定有所不一樣:

// NaN
NaN === NaN  // false
Object.is(NaN, NaN)  // true

// +0 -0
+0 === -0  // true
Object.is(+0, -0)  // false
複製代碼

3、類型判斷

我是誰?我從哪裏來?我要到哪裏去?——《人生三問》

對變量類型的判斷是代碼健壯的基礎,只有正確判斷變量的類型,才能夠安心調用變量上部署的方法,如string.charAt() array.map()等。那麼有哪些方法能夠判斷類型呢?

typeof x

typeof是JavaScript內置的一個用於判斷類型的一元操做符,它返回一個表示類型的字符串。下表總結了typeof可能的返回值:

typeof x Result
Undefined "undefined"
Boolean "boolean"
Number "number"
String "string"
Symbol "symbol"
BigInt "bigint"
Function "function"
Null "object"
其餘 "object"

從上表能夠看出,typeof判斷基本類型時很是合適,能夠正確返回咱們指望的類型字符串。而操做數是對象或者null時,則統一返回"object"字符串,則須要其餘的方式來進一步判斷類型。

所以,能夠得出一個結論:若是你明確變量是基礎類型時,請使用typeof操做符來判斷類型。其餘類型typeof則有些力不從心了。

({}).toString.call(x)

當須要判斷更多類型的時候,toString老大哥就勇敢站出來了,須要注意的是toStringObject原型鏈上的方法。其實,每一個內置對象都有一個toString方法,不一樣對象的toString的行爲都是不同,但它們都是繼承自Object{}.toString.call(x)返回一個字符串[object Type],其中Type可能取值爲 Number String Boolean Function Undefined Null Object Symbol Date Math RegExp ...

ECMA標準這樣描述:

  1. x is undefind, return [object Undefined];
  2. x is null,return [object Null];
  3. let O = toObject(x);
  4. return "[object " + classOf(O) + "]"

({}).toString.call(x)能夠很方便的判斷JS的內置對象類型,它進一步細化了object分支裏的大部分類型,適用於須要更多類型判斷場景,如下是type工具函數的示例:

function type(obj) {
    let toString = Object.prototype.toString,
        typeReg = /\[object\s([A-Z][a-z]*)\]/,
        matchArr = toString.call(obj).match(typeReg)

    return matchArr ? matchArr[1].toLowerCase() : ''
}
複製代碼

對象的toString 方法可使用Symbol.toStringTag 這個特殊的對象屬性進行自定義輸出(詳細可參考ES6——Symbol)。舉例說明:

let user = {
    [Symbol.toStringTag]: 'User'
}
console.log(({}).toString.call(user)  // [object User]
複製代碼

如此你能夠爲你本身的對象或類定義個性化的類型字符串。宿主環境的大部分環境相關對象用的就是這個原理:

console.log(({}).toString.call(window))  // [object Window]
複製代碼

x instanceof XXX

typeof + toString的強強聯合,已經包攬了80%的類型判斷場景,但仍有20%的自定義對象類型場景無能爲力,例如你有一個自定義類Person:

class Person {}
let person = new Person()
typeof person // "object"
{}.toString.call(person) // "[object Object]"
複製代碼

此時,你須要instanceof運算符來檢測構造函數的 prototype 屬性是否出如今 person 實例對象的原型鏈上。 有點繞口,至於何爲原型鏈,就不在這裏展開了。 x instanceof X,字面意思 x 是否由 X 實例化,返回 true/false。

person instanceof Person // true
// or
Object.getPrototypeOf(person) === Person.prototype
複製代碼

仍是instanceof比較直觀,😃

4、顯式類型轉換

隱式類型轉換是一把雙刃劍。它幫開發者自動轉換類型,省去麻煩;有時候又出其不意,莫名其妙。顯式類型轉換架起基礎類型防線。

+x or Number(x)

+一元正號運算符,計算操做數的數值,若是這個操做數不是數值,則嘗試將其轉換爲數值。根據標準描述,+x其實就是執行的toNumber(x)操做:

+x Result
Boolean 0 / 1
Number 值自己
String 轉換爲數值
Null +0
Undefined NaN
Symbol throw a TypeError
Object ToPrimitive(x),再重複上面步驟
通常是執行valueOf 或 toString方法

同理,Number(x)執行的過程也是toNumber(x)的過程。

''+x or String(x)

'' + x中的加號不一樣於 +x中的加號,此處的加號的做用是字符串的拼接。它會顯示的將不是字符串類型的操做數轉換爲字符串後拼接,即toString操做。 從toString(x)小結中,咱們知道,在對象上運行toString方法時,會在原型鏈上查找到最近的toString方法運行。通常而言,繼承自Object的其餘對象都會實現本身的toString方法。

"" + 34  // 等價於 "" + (34).toString()
"" + true  //等價於 "" + (true).toString()
"" + {}  //等價於 "" + ({}).toString()
複製代碼

直接調用String構造函數來顯示轉換類型,和""+x基本一致。但ES6中新增的基本類型Symbol則不適用直接調用""+x方式來轉換爲字符串,會異常報錯。

// Error
"" + Symbol('JS')  // Uncaught TypeError: Cannot convert a Symbol value to a string at <anonymous>

String(Symbol('JS'))
// or
(Symbol('JS')).toString()
複製代碼

看起來,String(x)要比''+x靠譜的多。

!!x

雙重非!!運算符顯式將任意值強制轉換爲布爾值。 還記得剛纔提到的JS中的七個假值嗎?即,x是七個假值時,!!x必定返回的 false,其餘狀況都返回的 ture。

總結

  1. JS中存在七個假值(Falsy),undefined null "" false 0 0n NaN
  2. ===全等運算符比==相等運算符更加嚴格,且更符合相等性判斷預期;
  3. typeoftoString的類型判斷方法能夠覆蓋80%的類型判斷場景,夠用;
  4. 顯示類型轉換能夠減小隱式類型轉換的不肯定性;

ECMA參考標準

相關文章
相關標籤/搜索