當我第一次看到這一題目的時候,我是比較震驚的,分析了下很不合咱們編程的常理,並認爲不大可能,變量a
要在同一狀況下要同時等於1,2和3這三個值,這是天方夜譚吧,不亞於哥德巴赫1+1=1
的猜測吧,不過一切皆有可能,出於好奇心,想了許久以後我仍是決定嘗試解決的辦法。git
個人思路來源於更早前遇到的另一題類似的面試題:github
// 設置一個函數輸出一下的值 f(1) = 1; f(1)(2) = 3; f(1)(2)(3) = 6; 複製代碼
當時的解決辦法是使用toString
或者valueOf
實現的,那咱們先回顧下toString
和valueOf
方法,方便咱們更深刻去了解這類型的問題:面試
好比咱們有一個對象,在不重寫toString()
方法和valueOf()
方法的狀況下,在 Node 或者瀏覽器輸出的結果是這樣的編程
class Person { constructor() { this.name = name; } } const best = new Person("Kobe"); console.log(best); // log: Person {name: "Kobe"} console.log(best.toString()); // log: [object Object] console.log(best.valueOf()); // log: Person {name: "Kobe"} console.log(best + "GiGi"); // log: [object Object]GiGi 複製代碼
best | Person |
best.toString() | [object Object] |
best.valueOf() | Person |
best + 'GiGi' | [object Object]GiGi |
從上面的輸出咱們能夠觀察到一個細節,toString()
輸出的是[object Object]
,而valueOf()
輸出的是Person
對象自己,而當運算到best + 'GiGi'
的時候居然是輸出了[object Object]GiGi
,咱們能夠初步推斷是對象調用的toString()
方法獲得的字符串進行計算的,難道是運算符+
的鬼斧神工嗎?數組
爲了驗證咱們上一步的推斷,咱們稍微作一點改變,把 valueOf
方法進行一次複寫:瀏覽器
class Person { constructor(name) { this.name = name; } // 複寫 valueOf 方法 valueOf() { return this.name; } } 複製代碼
best | Person |
best.toString() | [object Object] |
best.valueOf() | Person |
best + 'GiGi' | KobeGiGi |
此次跟上面只有一處產生了不同的結果,那就是最後的best + 'GiGi'
先後兩次結果在複寫了valueOf()
方法以後發生了改變,從中咱們能夠看出來,對象的本質其實沒有發生根本的改變,可是當它被用做直接運算的時候,它的值是從複寫的valueOf()
中獲取的,並繼續參與後續的運算。markdown
固然不要忘了咱們還有個toString()
方法,因此咱們也複寫它,看看結果會不會也受影響:函數
class Person { constructor(name) { this.name = name; } valueOf() { return this.name; } toString() { return `Bye ${this.name}`; } } 複製代碼
best | Person |
best.toString() | Bye Kobe |
best.valueOf() | Kobe |
best + 'GiGi' | KobeGiGi |
咱們發現 best + 'GiGi'
仍是沒有發生任何改變,仍是使用咱們上一次複寫valueOf()
的結果工具
其實咱們重寫了valueOf
方法,不是必定調用valueOf()
的返回值進行計算的。而是valueOf
返回的值是基本數據類型時纔會按照此值進行計算,若是不是基本數據類型,則將使用toString()
方法返回的值進行計算。oop
class Person { constructor(name) { this.name = name; } valueOf() { return this.name; } toString() { return `Bye ${this.name}`; } } const best = new Person({ name: "Kobe" }); console.log(best); // log: Person name: {name: "Kobe"} console.log(best.toString()); // log: Bye [object Object] console.log(best.valueOf()); // log: Person {name: "Kobe"} console.log(best + "GiGi"); // log: [object Object]GiGi 複製代碼
best | Person |
best.toString() | Bye [object Object] |
best.valueOf() | {name: "Kobe"} |
best + 'GiGi' | Bye [object Object]GiGi |
看上面的例子,如今傳入的name
是一個對象new Person({ name: "Kobe" })
,並非基本數據類型,因此當執行加法運算的時候取toString()
方法返回的值進行計算,固然若是沒有valueOf()
方法,就會去執行toString()
方法。
因此鋪墊了這麼久,咱們就要揭開答案,咱們正是使用上面這些原理去解答這一題:
class A { constructor(value) { this.value = value; } toString() { return this.value++; } } const a = new A(1); if (a == 1 && a == 2 && a == 3) { console.log("Hi Eno!"); } 複製代碼
這裏就比較簡單,直接改寫toString()
方法,因爲沒有valueOf()
,當他作運算判斷a == 1
的時候會執行toString()
的結果。
class A { constructor(value) { this.value = value; } valueOf() { return this.value++; } } const a = new A(1); if (a == 1 && a == 2 && a == 3) { console.log("Hi Eno!"); } 複製代碼
固然,你也能夠不使用toString
,換成valueOf
也行,效果也是同樣的:
class A { constructor(value) { this.value = value; } valueOf() { return this.value++; } } const a = new A(1); console.log(a); if (a == 1 && a == 2 && a == 3) { console.log("Hi Eno!"); } 複製代碼
因此,當一個對象在作運算的時候(好比加減乘除,判斷相等)時候,每每會有valueOf()
或者toString
的調用問題,這個對象的變量背後一般隱藏着一個函數。
固然下面這題原理其實也是同樣的,附上解法:
// 設置一個函數輸出一下的值 f(1) = 1; f(1)(2) = 3; f(1)(2)(3) = 6; function f() { let args = [...arguments]; let add = function() { args.push(...arguments); return add; }; add.toString = function() { return args.reduce((a, b) => { return a + b; }); }; return add; } console.log(f(1)(2)(3)); // 6 複製代碼
固然尚未結束,這裏還會有一些特別的解法,其實在使用對象的時候,若是對象是一個數組的話,那麼上面的邏輯仍是會成立,但此時的toString()
會變成隱式調用join()
方法,換句話說,對象中若是是數組,當你不重寫其它的toString()
方法,其默認實現就是調用數組的join()
方法返回值做爲toString()
的返回值,因此這題又多了一個新的解法,就是在不復寫toString()
的前提下,複寫join()
方法,把它變成shift()
方法,它能讓數組的第一個元素從其中刪除,並返回第一個元素的值。
class A extends Array { join = this.shift; } const a = new A(1, 2, 3); if (a == 1 && a == 2 && a == 3) { console.log("Hi Eno!"); } 複製代碼
咱們的探尋之路還沒結束,細心的同窗會發現咱們題目是如何讓(a===1&&a===2&&a===3)的值爲 true
,可是上面都是討論寬鬆相等==
的狀況,在嚴格相等===
的狀況下,上面的結果會不一樣嗎?
答案是不同的,大家能夠試試把剛纔上面的寬鬆條件改爲嚴格調試再試一次就知道結果了。
class A extends Array { join = this.shift; } const a = new A(1, 2, 3); // == 改爲 === 後: if (a === 1 && a === 2 && a === 3) { console.log("Hi Eno!"); // Hi Eno!此時再也沒出現過了 } 複製代碼
那麼此時的狀況又要怎麼去解決呢?咱們能夠考慮一下使用Object.defineProperty
來解決,這個由於Vue
而被衆人熟知的方法,也是如今面試中一個老生常談的知識點了,咱們可使用它來劫持a
變量,當咱們獲取它的值得時候讓它自增,那麼問題就能夠迎刃而解了:
var value = 1; Object.defineProperty(window, "a", { get() { return this.value++; } }); if (a === 1 && a === 2 && a === 3) { console.log("Hi Eno!"); } 複製代碼
上面咱們就是劫持全局window
上面的a
,當a
每一次作判斷的時候都會觸發get
屬性獲取值,而且每一次獲取值都會觸發一次函數實行一次自增,判斷三次就自增三次,因此最後會讓公式成立。
固然這裏還有其餘方法,這裏再舉例一個,好比使用隱藏字符去作障眼法瞞過面試官的:
var aᅠ = 1; var a = 2; var ᅠa = 3; if (aᅠ == 1 && a == 2 && ᅠa == 3) { console.log("Hi Eno!"); } 複製代碼
上面這種解法的迷惑性很強,若是不細心會覺得是三個同樣的a
,其實本質上是定義三個不同的a
值,a
的先後都有隱藏的字符,因此調試的時候,請複製粘貼上面的代碼調試,本身在Chrome
手打的話能夠用特殊手段讓 a
後面放一個或者兩個紅點實現,並在回車的時候,調試工具會把這些痕跡給隱藏,從而瞞天過海,秀到一時半刻還沒反應過來的面試官。
最後,祝願你們在新的一年找到一份如意的工做,上面的代碼在實際狀況中基本是不會被運用到的,可是用來探索JS
的無限多是具備啓發性的,也建議面試官不要使用這類面試題去難爲面試者~
若是文章和筆記能帶您一絲幫助或者啓發,請不要吝嗇你的贊和收藏,你的確定是我前進的最大動力😁
附筆記連接,閱讀往期更多優質文章可移步查看,喜歡的能夠給我點贊鼓勵哦: