最近的一道热门的题目 `Can (a ==1 && a== 2 && a==3) ever evaluate to true?
`, 引发了不少人的关注与讨论,本身能够很容易想到一种实现,可是看你们讨论出来的答案,其中有不少有意思的,不得不佩服一些人的脑洞,其中不少原理也值得探讨。javascript不少其余语言的程序员对于这样的结果,不少都归结于
果真javascript
之类,的确与一些语言不一样,javascript
中除了==
外还有===
,===
叫作严格运算符,==
叫作相等运算符。java
其实 JavaScript
一共提供了8个比较运算符。程序员
< 小于运算符 > 大于运算符 <= 小于或等于运算符 = 大于或等于运算符 == 相等运算符 === 严格相等运算符 != 不相等运算符 !== 严格不相等运算符
比较操做涉及多不一样类型的值时候,会涉及到不少隐式转换,其中规则繁多即使是经验老道的程序员也没办法彻底记住,特别是用到 ==
和 !=
运算时候。因此一些团队规定禁用 ==
运算符换用===
严格相等。以工程标准衡量,==
带来的便利性抵不上其带来的成本,团队协做时候你看到别人代码中的 ==
,有些时候须要判断清楚做者的代码意图是确实须要转型,仍是无所谓要不要转型只是随手写了,增长了一些额外的成本。可是我比较喜欢的一本书 You don't know JS
,中做者也写道过一个我比较赞同的观点es6
Many developers feel that === is more predictable, so they advocate always using that form and staying away from ==. I think this view is very shortsighted. I believe == is a powerful tool that helps your program, if you take the time to learn how it works.算法
简译为数组
不少开发者认为 === 的行为更加容易预测,从而主张使用 === 而远离 ==。我认为这种观点是很是短视的,若是你花点时间去搞清楚它的工做原理,== 将是你开发的强大工具。闭包
究竟谁对谁错也没有定论,可是去了解一些隐式转换的规则,对咱们开发和调试bug都很是有帮助。这些规则规定很烦多,一个个看过来很无趣也不能全记住。咱们下面从这个问题的一些答案中去探究其中的一小部分规则。函数
var aᅠ = 1; var a = 2; var ᅠa = 3; if(aᅠ==1 && a== 2 &&ᅠa==3) { console.log("Why hello there!") }
看到这个答案时候曾一度怀疑本身学的是假的 javascript
,这个答案和隐式转换没有关系,能够说它能考察你的找茬能力,注意if里面的空格,它是一个Unicode空格字符,不被ECMA脚本解释为空格字符(这意味着它是标识符的有效字符)
。因此它能够解释为工具
var a_ = 1; var a = 2; var _a = 3; if(a_==1 && a== 2 &&_a==3) { console.log("Why hello there!") }
const a = { i: 1, toString: function () { return a.i++; } } if(a == 1 && a == 2 && a == 3) { console.log('Hello World!'); }
若是原始类型的值和对象比较,对象会转为原始类型的值,再进行比较。(我想到的也是这种方法)
,对象转换成原始类型的值,算法是先调用valueOf
方法;若是返回的仍是对象,再接着调用toString
方法。咱们每次比较时候都会执行方法返回 a
的 i
属性同时也改变 i
的值,因此上面 if
执行完之后 a
的 i
属性已经变为了 4,这里也表现出了 == 比较是有可能会对变量带来反作用的this
var a = [1,2,3]; a.join = a.shift; console.log(a == 1 && a == 2 && a == 3);
这个答案仍是比较巧妙的,咱们知道 array
也属于对象,应该和对象的规则同样。关于 array
的原型链上的 toString
方法
对于数组对象,toString 方法返回一个字符串,该字符串由数组中的每一个元素的 toString() 返回值经调用 join() 方法链接(由逗号隔开)组成。
能够看到数组 toString
会调用自己的 join
方法,这里把本身的join
方法该写为shift
,每次返回第一个元素,并且原数组删除第一个值,正好可使判断成立。这里 == 比较也带来的反作用
var i = 0; with({ get a() { return ++i; } }) { if (a == 1 && a == 2 && a == 3) console.log("wohoo"); }
with
也是被严重建议不使用的对象,这里也是利用它的特性在代码块里面利用对象的 get
方法动态返回 i
.
var val = 0; Object.defineProperty(window, 'a', { get: function() { return ++val; } }); if (a == 1 && a == 2 && a == 3) { console.log('yay'); }
咱们知道咱们用的全局变量也至关于 window
对象上的一个属性,这里用defineProperty
定义了 a
的 get
也使得其动态返回值。和with
有一些相似。
let a = {[Symbol.toPrimitive]: ((i) => () => ++i) (0)}; console.log(a == 1 && a == 2 && a == 3);
ES6
引入了一种新的原始数据类型Symbol
,表示独一无二的值。咱们以前在定义类的内部私有属性时候习惯用 __xxx
,这种命名方式避免别人定义相同的属性名覆盖原来的属性,有了 Symbol
以后咱们彻底能够用 Symbol
值来代替这种方法,并且彻底不用担忧被覆盖。
除了定义本身使用的 Symbol
值之外,ES6
还提供了 11 个内置的 Symbol
值,指向语言内部使用的方法。Symbol.toPrimitive
就是其中一个,它指向一个方法,表示该对象被转为原始类型的值时,会调用这个方法,返回该对象对应的原始类型值。这里就是改变这个属性,把它的值改成一个 闭包
返回的函数。