在 JavaScript 环境下,可以让表达式 a == true && a == false 为 true 吗?
就像下面这样,可以在控制台打印出 ’yeah’:
// code here
if (a == true && a == false) {
console.log('yeah');
}
JavaScript 是一门类型松散的语言,在使用 ==
进行比较时,倘若左右类型不一致,是会进行类型装换的。首先来了解一下宽松相等的概念,
宽松相等 ==
先看看 ECMA 5.1 的规范,包含 toPrimitive
:
规范
11.9.3 The Abstract Equality Comparison Algorithm
If Type(x) is the same as Type(y), then
- If Type(x) is Undefined, return true.
- If Type(x) is Null, return true.
If Type(x) is Number, then
- If x is NaN, return false.
- If y is NaN, return false.
- If x is the same Number value as y, return true.
- If x is +0 and y is −0, return true.
- If x is −0 and y is +0, return true.
- Return false.
- If Type(x) is String, then return true if x and y are exactly the same sequence of characters (same length and same characters in corresponding positions). Otherwise, return false.
- If Type(x) is Boolean, return true if x and y are both true or both false. Otherwise, return false.
- Return true if x and y refer to the same object. Otherwise, return false.
- If x is null and y is undefined, return true.
- If x is undefined and y is null, return true.
- If Type(x) is Number and Type(y) is String, return the result of the comparison x == ToNumber(y).
- If Type(x) is String and Type(y) is Number, return the result of the comparison ToNumber(x) == y.
- If Type(x) is Boolean, return the result of the comparison ToNumber(x) == y.
- If Type(y) is Boolean, return the result of the comparison x == ToNumber(y).
- If Type(x) is either String or Number and Type(y) is Object, return the result of the comparison x == ToPrimitive(y).
- If Type(x) is Object and Type(y) is either String or Number, return the result of the comparison ToPrimitive(x) == y.
- Return false.
9.1 ToPrimitive
Table 10 — ToPrimitive Conversions
Input Type | Result |
---|---|
Undefined | The result equals the input argument (no conversion). |
Null | The result equals the input argument (no conversion). |
Boolean | The result equals the input argument (no conversion). |
Number | The result equals the input argument (no conversion). |
String | The result equals the input argument (no conversion). |
Object | Return a default value for the Object. The default value of an object is retrieved by calling the [[DefaultValue]] internal method of the object, passing the optional hint PreferredType. The behaviour of the [[DefaultValue]] internal method is defined by this specification for all native ECMAScript objects in 8.12.8. |
稍作总结
对于下述表达式:
x == y
- 类型相同,判断的就是 x === y
类型不同
- 如果 x,y 其中一个是布尔值,将这个布尔值进行 ToNumber 操作
- 如果 x,y 其中一个是字符串,将这个字符串进行 ToNumber 操作
- 若果 x,y 一方为对象,将这个对象进行 ToPrimitive 操作
至于 ToPrimitive
,即求原始值,可以简单理解为进行 valueOf()
和 toString()
操作。
稍后我们再详细剖析,接下来先看一个问题。
Question:是否存在这样一个变量,满足 x == !x
就像这样:
// code here
if (x == !x) {
console.log('yeah');
}
可能很多人会想到下面这个,毕竟我们也曾热衷于各种奇技淫巧:
[] == ![] // true
但答案绝不仅仅局限于此,比如:
var x = new Boolean(false);
if (x == !x) {
console.log('yeah');
}
理解这个问题,基本上下面的这些例子都不是问题了。
宽松相等的栗子
9 == '9'
9 == '9x'
9 == true
9 == undefined
9 == null
0 == undefined
0 == null
undefined == null
0 == false
'' == false
'1' == true
'9' == true
9 == [9]
'9' == [9]
'9' == [9, 4]
'9,4' == [9, 4]
[] == []
[] == ![]
![] == ![]
[] == {}
[] == !{}
{} == ![]
{} == {}
{} == !{}
9 == { toString() { return 9 }}
9 == { valueOf() { return 9 }}
9 == { a: 9 }
9 == {}
'[object Object]' == {}
在来看看什么是 ToPrimitive
ToPrimitive
贴个规范:8.12.8 [[DefaultValue]] (hint)
如果是 Date
求原始值,则 hint 是 String
,其他均为 Number
,即先调用 valueOf()
再调用 toString()
。
如果 hint 为 Number
,具体过程如下:
- 调用对象的
valueOf()
方法,如果值是原值则返回 - 否则,调用对象的
toString()
方法,如果值是原值则返回 - 否则,抛出 TypeError 错误
// valueOf 和 toString 的调用顺序
var a = {
valueOf() {
console.log('valueof')
return []
},
toString() {
console.log('toString')
return {}
}
}
a == 0
// valueof
// toString
// Uncaught TypeError: Cannot convert object to primitive value
// Date 类型先 toString,后 valueOf
var t = new Date('2018/04/01');
t.valueOf = function() {
console.log('valueof')
return []
}
t.toString = function() {
console.log('toString')
return {}
}
t == 0
// toString
// valueof
// Uncaught TypeError: Cannot convert object to primitive value
到目前为止,上面的都是 ES5 的规范,那么在 ES6 中,有什么变化呢
ES6 中 ToPrimitive
7.1.1ToPrimitive ( input [, PreferredType] )
在 ES6 中吗,是可以自定义 @@toPrimitive 方法的,是 Well-Known Symbols(§6.1.5.1)中的一个。JavaScript 还内建了一些在 ECMAScript 5 之前没有暴露给开发者的 symbol,它们代表了内部语言行为。
// 没有 Symbol.toPrimitive 属性的对象
var obj1 = {};
console.log(+obj1); // NaN
console.log(`${obj1}`); // '[object Object]'
console.log(obj1 + ''); // '[object Object]'
// 拥有 Symbol.toPrimitive 属性的对象
var obj2 = {
[Symbol.toPrimitive](hint) {
if (hint == 'number') {
return 10;
}
if (hint == 'string') {
return 'hello';
}
return true;
}
};
console.log(+obj2); // 10 -- hint is 'number'
console.log(`${obj2}`); // 'hello' -- hint is 'string'
console.log(obj2 + ''); // 'true' -- hint is 'default'
有了上述铺垫,答案就呼之欲出了
最初题目的答案
var a = {
flag: false,
toString() {
return this.flag = !this.flag;
}
}
或者使用 valueOf()
:
var a = {
flag: false,
valueOf() {
return this.flag = !this.flag;
}
}
或者是直接改变 ToPrimitive 行为:
// 其实只需设置 default 即可
var a = {
flag: false,
[Symbol.toPrimitive](hint) {
if (hint === 'number') {
return 10
}
if (hint === 'string') {
return 'hello'
}
return this.flag = !this.flag
}
}
如果是严格相等呢
但是,有没有办法是严格相等,即:
// code here
if (a === true && a === false) {
console.log('yeah');
}
答案是:有。
使用 defineProperty
,可能有不少朋友一开始就想到这种方式,简单贴一下:
let flag = false
Object.defineProperty(window, 'a', {
get() {
return (flag = !flag)
}
})
if (a === true && a === false) {
console.log('yeah');
}