1

在 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

  1. If Type(x) is the same as Type(y), then

    1. If Type(x) is Undefined, return true.
    2. If Type(x) is Null, return true.
    3. If Type(x) is Number, then

      1. If x is NaN, return false.
      2. If y is NaN, return false.
      3. If x is the same Number value as y, return true.
      4. If x is +0 and y is −0, return true.
      5. If x is −0 and y is +0, return true.
      6. Return false.
    4. 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.
    5. If Type(x) is Boolean, return true if x and y are both true or both false. Otherwise, return false.
    6. Return true if x and y refer to the same object. Otherwise, return false.
  2. If x is null and y is undefined, return true.
  3. If x is undefined and y is null, return true.
  4. If Type(x) is Number and Type(y) is String, return the result of the comparison x == ToNumber(y).
  5. If Type(x) is String and Type(y) is Number, return the result of the comparison ToNumber(x) == y.
  6. If Type(x) is Boolean, return the result of the comparison ToNumber(x) == y.
  7. If Type(y) is Boolean, return the result of the comparison x == ToNumber(y).
  8. If Type(x) is either String or Number and Type(y) is Object, return the result of the comparison x == ToPrimitive(y).
  9. If Type(x) is Object and Type(y) is either String or Number, return the result of the comparison ToPrimitive(x) == y.
  10. 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,具体过程如下:

  1. 调用对象的 valueOf() 方法,如果值是原值则返回
  2. 否则,调用对象的 toString() 方法,如果值是原值则返回
  3. 否则,抛出 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');
}

阅读更多


离独逸
889 声望493 粉丝