[译] 在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社区,每周定时推送优质文章。

相关文章
相关标签/搜索