原文: 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 == B
与B == A
具有相同的表现(除了转换的顺序不同)。
Javascript 会如何强制转换这个值呢?
在进行两个值的比较时,执行了类型的强制转换, 让我们先了解下内置的转换函数。
ToPrimitive(input, PreferredType?)
可选参数 PreferredType
可以指定最终转化的类型,它可以是 Number
类型或 String
类型,这依赖于 ToPrimitive()
方法执行的结果返回的是 Number
类型或 String
类型。
值的转化过程如下:
- 如果输入
Input
是基本类型, 就返回这个值; - 如果输入变量是
Object
类型, 那么调用input.valueOf()
; 如果返回结果是基本类型,就返回这个值; - 如果都不是的话就调用
input.toString()
; 如果结果是基本类型,就返回它; - 如果以上都不可以,就会抛出一个类型错误
TypeError
, 表示转化 input 变量到基本类型失败。
如果 PreferredType
是 Number
, 那转换算法就会像上述说明的顺序执行,如果是 String
,步骤2 和 步骤3 会交换顺序。PreferredType
是一个缺省值,如果不输入的话,Date
类型会被当作 String
类型处理,其他变量会当作 Number
处理。默认的 valueOf
返回 this
,默认的 toString()
会返回类型信息。
如上是操作符 +
和 ==
调用 toPrimitive()
的执行过程。
所以在上面的代码中,如JS引擎所解析的,a == 1
, 1
是基本类型,JS 引擎会尝试将 a
转换成 Number
类型,然后在上面的算法中,a.valueOf
被调用并且返回1 (自增1并且返回自己)。在 a==2
和 a==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) ?
属性描述符有两种类型, 数据描述符和存取描述符。
-
数据描述符
强制键值 - value
可选键值:
- configurable - enumable - writeable
🌰例子:
{ value: 5, writable: true }
-
存取描述符
强制键值 - 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
为对象定义了一个属性。 有趣的是,get
和 set
是可以通过 .
操作符调用的方法,举个例子,a
有一个具有 getter
的 b
属性,它可以像对象的其他属性一样去调用,类似于 a.b
。这可以解决我们最初的问题, 我们需要调用一个无需 ()
的函数, 通过 get
属性,我们可以调用一个函数并且不用在函数名后添加 ()
。
作者:腾讯IVWEB团队
链接:https://juejin.im/post/5bfcc632f265da61493353cc
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。