魔幻語言 JavaScript 系列之 a == true && a == false

在 JavaScript 環境下,可讓表達式 a == true && a == false 爲 true 嗎?html

就像下面這樣,能夠在控制檯打印出 ’yeah':this

// code here
if (a == true && a == false) {
    console.log('yeah');
}

JavaScript 是一門類型鬆散的語言,在使用 == 進行比較時,假若左右類型不一致,是會進行類型裝換的。首先來了解一下寬鬆相等的概念,lua

寬鬆相等 ==

先看看 ECMA 5.1 的規範,包含 toPrimitive:code

規範

11.9.3 The Abstract Equality Comparison Algorithm

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

    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 Conversionsip

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.

稍做總結

對於下述表達式:ci

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');
}

閱讀更多

相關文章
相關標籤/搜索