在 JS 中,如何让(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
我们先简单了解 这道 JS 经典问题, 然后再解决它的扩展问题。
内容概览:
- 重温(a==1&&a==2&&a==3)(宽松相等)问题
- (a===1&&a===2&&a===3)扩展问题
- 问题答案与解释
- 参考文章
重温(a==1&&a==2&&a==3)(宽松相等)问题
如果你已经了解过这个问题并且知道如何解决这个 JS 谜题(是的,只是一个谜题,我并不想在生产代码中看到这样的用例) , 那你可以直接跳到下一节,阅读它的扩展问题。关于这个问题在reddit上有相关讨论, 下面是我看到最有趣的评论
"如果我在代码库中看到这样的代码,我可能就很绝望了" // 译者注: 谁看到都会很绝望吧
(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 中关于==
的奇特的语法特性。
问题解析
秘密就在于"宽松相等操作符 ==
"
在 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
为对象定义了一个属性。你可以在这里深入了解Object.defineProperty
的语法与定义。 有趣的是,get
和 set
是可以通过"."操作符
调用的方法, 举个例子, a
有一个具有getter
的b
属性, 它可以像对象的其他属性一样去调用,类似于a.b
。这可以解决我们最初的问题, 我们需要调用一个无需()
的函数, 通过get
属性, 我们可以调用一个函数并且不用在函数名后添加()
在上面提到的解决方案中, 我们在 window 对象上定义了一个 具有 getter 的a
属性, 所以a
可以在代码中直接被访问到(全局变量), 因此也可以直接获得 a 的值。如果我们在其他对象上定义了属性a
而不是 window 的话,例如 object1, 我们就需要改变题目为object1.a===1 && object1.a===2 && object1.a===3
了。
Github Gist
参考
← 反思闭包 将嵌套数组转换为一维数组 →