[譯] 在JS中,如何讓(a===1 && a===2 && a === 3)(嚴格相等)的值爲true?

原文: Will (a===1 && a===2 && a===3) (strict comparison) ever be true (in JavaScript)javascript

本文是JS經典問題(a == 1 && a==2 && a==3)(寬鬆相等)的擴展和解決方案html

如何使用getter/setter描述符讓(a===1 && a===2 && a === 3)的值爲true

咱們先簡單瞭解這道JS經典問題, 而後再解決它的擴展問題。java

內容概覽:git

重溫(a==1&&a==2&&a==3)(寬鬆相等)問題

若是你已經瞭解過這個問題而且知道如何解決這個JS謎題(是的,只是一個謎題,我並不想在生產代碼中看到這樣的用例) , 那你能夠直接跳到下一節,閱讀它的擴展問題。關於這個問題在reddit上有相關討論, 下面是我看到最有趣的評論github

"若是我在代碼庫中看到這樣的代碼,我可能就很絕望了" // 譯者注: 誰看到都會很絕望吧web

(a==1 && a== 2 && a ==3 )問題

(a ==1 && a==2 && a==3) 的值能夠是true嗎?面試

回答是確定的, 具體能夠看下面的代碼算法

const a = { value : 0 };
a.valueOf = function() {
    return this.value += 1;
};

console.log(a==1 && a==2 && a==3); //true
複製代碼

一般, 在面試中問這類問題的目的並非要求面試者記住這樣的答案,而是想要了解面試者在面對這道題目時,是如何思考的以及他們是否有了解過Javascript中關於==的奇特的語法特性。bash

問題解析

祕密就在於"寬鬆相等操做符 == "less

在JS中,寬鬆相等==會先將左右兩兩邊的值轉化成相同的原始類型,而後再去比較他們是否相等。在轉化以後(==一邊或兩邊都須要轉化),最後的相等匹配會像===符號同樣去執行判斷。寬鬆相等是可逆的,對於任何值A與B,一般A == BB == A具備相同的表現(除了轉換的順序不一樣)。能夠在這裏詳細深度地瞭解寬鬆匹配==與嚴格匹配===

Javascript會如何強制轉換這個值呢?

在進行兩個值的比較時,執行了類型的強制轉換, 讓咱們先了解下內置的轉換函數。

ToPrimitive(input, PreferredType?)
複製代碼

可選參數PreferredType能夠指定最終轉化的類型,它能夠是Number類型或String類型,這依賴於ToPrimitive()方法執行的結果返回的是Number類型或String類型。

值的轉化過程以下

  1. 若是輸入Input是基本類型, 就返回這個值
  2. 若是輸入變量是Object類型, 那麼調用input.valueOf(). 若是返回結果是基本類型,就返回這個指
  3. 若是都不是的話就調用input.toString(). 若是結果是基本類型, 就返回它
  4. 若是以上都不能夠,就會拋出一個類型錯誤TypeError, 表示轉化input變量到基本類型失敗。

若是PreferredTypeNumber, 那轉換算法就會像上述說明的順序執行,若是是String,步驟2和步驟3會交換順序。PreferredType是一個缺省值,若是不輸入的話,Date類型會被看成String類型處理,其餘變量會看成Number處理。默認的valueOf返回this,默認的toString()會返回類型信息。

如上是操做符+==調用toPrimitive()的執行過程。

因此在上面的代碼中, 如JS引擎所解析的,a == 11是基本類型, JS引擎會嘗試將a轉換成Number類型,而後在上面的算法中,a.valueOf被調用而且返回1(自增1而且返回本身)。在a==2a==3發生了一樣的類型轉換並增長本身的值。

(a === 1 && a === 2 && a === 3)(嚴格匹配) 問題

(a === 1 && a === 2 && a ===3)的值也能是true嗎?

固然也能夠, 具體請看下面的代碼

var value = 0; //window.value
Object.defineProperty(window, 'a', {
    get: function() {
        return this.value += 1;
    }
});

console.log(a===1 && a===2 && a===3) // true
複製代碼

問題解釋

從經典問題的解答中,咱們瞭解到JS中的原始類型將再也不知足於上面的條件(嚴格相等沒有轉化的過程),因此咱們須要經過一些方式去調用一個函數,並在這個函數中作咱們想作的事情。可是執行函數每每須要在函數名字後引入()。而且因爲這裏不是寬鬆相等==valueOf將不會被JS引擎調用。Emmm, 有點棘手。還好有Property函數, 特別是getter描述符, 帶來了解決這個問題的辦法。

什麼是屬性描述符(property descriptors)?

屬性描述符有兩種類型, 數據描述符和存取描述符。

  1. 數據描述符

    強制鍵值 - value

    可選鍵值

    - configurable
     - enumable
     - writeable
    複製代碼

    例子

    {
        value: 5,
        writable: true
    }
    複製代碼
  2. 存取描述符

    強制鍵值 - get/set或都設定 可選鍵值 - confiturable - enumerable 例子

    {
        get: function () { return 5; },
        enumerable: true
    }
    複製代碼

MDN上關於存取描述符的例子

// Example of an object property added
    // with defineProperty with an accessor property descriptor

    var bValue = 38;

    Object.defineProperty(o, 'b', {
        // Using shorthand method names (ES2015 feature).
        // This is equivalent to:
        // get: function() { return bValue; },
        // set: function(newValue) { bValue = newValue; },
        get() { return bValue; },
        set(newValue) { bValue = newValue; },
        enumerable: true,
        configurable: true
    });
    o.b; // 38
    // 'b' property exists in the o object and its value is 38
    // The value of o.b is now always identical to bValue,
    // unless o.b is redefined
複製代碼

在問題的解決方案中, 咱們使用Object.defineProperty爲對象定義了一個屬性。你能夠在這裏深刻了解Object.defineProperty的語法與定義。 有趣的是,getset是能夠經過"."操做符調用的方法, 舉個例子, a有一個具備getterb屬性, 它能夠像對象的其餘屬性同樣去調用,相似於a.b。這能夠解決咱們最初的問題, 咱們須要調用一個無需()的函數, 經過get屬性, 咱們能夠調用一個函數而且不用在函數名後添加()

在上面提到的解決方案中, 咱們在window對象上定義了一個具備getter的a屬性, 因此a能夠在代碼中直接被訪問到(全局變量), 所以也能夠直接得到a的值。若是咱們在其餘對象上定義了屬性a而不是window的話,例如object1, 咱們就須要改變題目爲object1.a===1 && object1.a===2 && object1.a===3了。

Github Gist

參考


《IVWEB 技術週刊》 震撼上線了,關注公衆號:IVWEB社區,每週定時推送優質文章。

相關文章
相關標籤/搜索