JavaScript:使 `a===1 && a===2 && a === 3` 的值为true?

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

本文是JS经典问题 a == 1 && a==2 && a==3 (宽松相等)的扩展和解决方案。

如何使用 getter/setter 描述符让 a===1 && a===2 && a === 3 的值为 true?


🥛 重温 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
问题解析

秘密就在于 "宽松相等操作符 =="。

在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为对象定义了一个属性。 有趣的是,getset 是可以通过 . 操作符调用的方法,举个例子,a 有一个具有 getterb 属性,它可以像对象的其他属性一样去调用,类似于 a.b。这可以解决我们最初的问题, 我们需要调用一个无需 () 的函数, 通过 get 属性,我们可以调用一个函数并且不用在函数名后添加 ()



作者:腾讯IVWEB团队
链接:https://juejin.im/post/5bfcc632f265da61493353cc
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值