在 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
If Type(x) is the same as Type(y), thenhtm
If Type(x) is Number, then對象
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
類型不一樣開發
至於 ToPrimitive
,即求原始值,能夠簡單理解爲進行 valueOf()
和 toString()
操做。字符串
稍後咱們再詳細剖析,接下來先看一個問題。
就像這樣:
// 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
貼個規範:8.12.8 [[DefaultValue]] (hint)
若是是 Date
求原始值,則 hint 是 String
,其餘均爲 Number
,即先調用 valueOf()
再調用 toString()
。
若是 hint 爲 Number
,具體過程以下:
valueOf()
方法,若是值是原值則返回toString()
方法,若是值是原值則返回// 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 中,有什麼變化呢
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'); }