本文是 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)扩展问题
问题答案与解释
什么是属性描述符
参考文章
重温 (a==1&&a==2&&a==3)(宽松相等) 问题
如果你已经了解过这个问题并且知道如何解决这个 JS 谜题(是的, 只是一个谜题, 我并不想在生产代码中看到这样的用例) , 那你可以直接跳到下一节, 阅读它的扩展问题. 关于这个问题在上有相关讨论, 下面是我看到最有趣的评论
"如果我在代码库中看到这样的代码, 我可能就很绝望了" // 译者注: 谁看到都会很绝望吧
(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; //Windows.value
- Object.defineProperty(Windows, '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 属性, 我们可以调用一个函数并且不用在函数名后添加()
在上面提到的解决方案中, 我们在 Windows 对象上定义了一个具有 getter 的 a 属性, 所以 a 可以在代码中直接被访问到(全局变量), 因此也可以直接获得 a 的值. 如果我们在其他对象上定义了属性 a 而不是 Windows 的话, 例如 object1, 我们就需要改变题目为 object1.a===1 && object1.a===2 && object1.a===3 了.
GitHub Gist
- Problem (a==1 && a==2 && a==3)
- Problem (a===1 && a===2 && a===3)
参考
《IVWEB 技术周刊》 震撼上线了, 关注公众号: IVWEB 社区, 每周定时推送优质文章.
周刊文章集合: https://github.com/feflow/weekly
团队开源项目: Feflow https://github.com/feflow/feflow
来源: https://juejin.im/post/5bfcc632f265da61493353cc