<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          奇怪的知識增加了,如何讓 (a == 1 && a == 2 && a == 3) 返回 true

          共 5708字,需瀏覽 12分鐘

           ·

          2021-12-01 00:27

          點擊上方“碼農(nóng)突圍”,馬上關(guān)注

          這里是碼農(nóng)充電第一站,回復(fù)“666”,獲取一份專屬大禮包
          真愛,請設(shè)置“星標(biāo)”或點個“在看


          前兩天在網(wǎng)上看到了一道很有趣的題目,題目大意為:JS 環(huán)境下,如何讓 a == 1 && a == 2 && a == 3 這個表達式返回 true ?

          這道題目乍看之下似乎不太可能,因為在正常情況下,一個變量的值如果沒有手動修改,在一個表達式中是不會變化的。當(dāng)時我也冥思苦想很久,甚至一度懷疑這道題目的答案就是 不能。直到在 stackoverflow 上面竟然真的發(fā)現(xiàn)了解法 can-a-1-a-2-a-3-ever-evaluate-to-true[1]。

          讓這個表達式成為 true 的關(guān)鍵就在于這里的寬松相等,JS 在處理寬松相等時會對一些變量進行隱式轉(zhuǎn)換。在這種隱式轉(zhuǎn)換的作用下,真的可以讓一個變量在一個表達式中變成不同的值。

          寬松相等下的真值表

          最高票答案給出的解法為:

          const a = {
            i: 1,
            toString: function () {
              return a.i++;
            }
          }

          if (a == 1 && a == 2 && a == 3) {
            console.log('Hello World!');
          }

          看到這個答案,我才恍然大悟,這道題目的考點原來是 JS 獲取一個變量所需要做的操作以及其中一些細節(jié)。在 JS 中有 ===== 兩種方式來判斷兩個變量是否相等。對 JS 稍有了解的人都知道,=== 是嚴(yán)格相等,不僅需要兩個變量的值相同,還需要類型也相同,而 == 則是寬松下的相等,只需要值相同就能夠判斷相等,寬松相等是嚴(yán)格相等的子集。所以在 JS 中,嚴(yán)格相等的兩個變量一定也是寬松相等的,但是寬松相等的兩個變量,大多數(shù)情況下并不是嚴(yán)格相等的。例如:

          null == undefined // true
          null === undefined // false

          1 == '1' // true
          1 === '1' // false

          這也就出現(xiàn)了 JS 特有的,變量寬松相等判斷的真值表,里面列舉了所有在寬松相等比較的情況下,兩種變量可能出現(xiàn)的類型:


          在上面的表格中,ToNumber(A) 嘗試在比較前將參數(shù) A 轉(zhuǎn)換為數(shù)字,這與 +A(單目運算符+)的效果相同。ToPrimitive(A) 通過嘗試依次調(diào)用 A 的 A.toString() 和 A.valueOf() 方法,將參數(shù) A 轉(zhuǎn)換為原始值(Primitive)。

          從上圖中我們可以看到,當(dāng)操作數(shù) B 類型為 Number 時,如果希望在寬松相等的情況下整個表達式的結(jié)果返回 true,操作數(shù) A 必須滿足下面三個條件之一:

          1. 操作數(shù) A 類型為 String,并且調(diào)用 +A 的結(jié)果與 B 嚴(yán)格相等
          2. 操作數(shù) A 類型為 Boolean,并且調(diào)用 +A 的結(jié)果與 B 嚴(yán)格相等
          3. 操作數(shù) A 類型為 Object,并且調(diào)用 toString 或者 ValueOf 返回的結(jié)果與 B 嚴(yán)格相等

          在這里,如果我們要改變 +A 操作的結(jié)果相對來說比較困難,因為我們很難在 JS 中去重載 + 操作符的運算。但是在第三種情況下,使 A 的類型為 Object,調(diào)用 toString 或 ValueOf 結(jié)果與 B 嚴(yán)格相等讓我們自己實現(xiàn)就容易的多。

          所以上面的答案就是新建了一個對象 a ,并有 toString 方法,當(dāng) JS 引擎每次讀取 a 的值的時候,發(fā)現(xiàn)需要進行寬松判斷一個對象和一個數(shù)字之間的結(jié)果,對于對象就會執(zhí)行這里的 toString 方法,在這個方法內(nèi)部,我們每次增加另一個變量的值并返回,就能夠在這條表達式中使得 a 的結(jié)果有不同的值。

          同理,換一種寫法,aObject ,使用 valueOf 也是可以完成目標(biāo)的:

          cosnt a = {
            i: 1,
            valueOf() {
              return this.i++
            }
          }

          if (a == 1 && a == 2 && a == 3) {
            console.log('Hello World!');
          }

          寬松相等下的 Proxy 對象

          有了上面的思路,下面實現(xiàn)起來就容易的多。在 ES6 中 JS 新增了 Proxy 對象,能夠?qū)σ粋€對象進行劫持,接受兩個參數(shù),第一個是需要被劫持的對象,第二個參數(shù)也是一個對象,內(nèi)部也可以配置每一個元素的 get 方法:

          var a = new Proxy({ i: 1 }, {
            get(target) { return () => target.i++ }
          });

          if (a == 1 && a == 2 && a == 3) {
            console.log('Hello World!');
          }

          同樣的,Proxy 對象默認(rèn)的 toStringvalueOf 方法會返回這個被 getter 劫持過的結(jié)果,也能夠在寬松相等的條件下滿足題意。

          嚴(yán)格相等下的實現(xiàn)

          上面的這幾種做法,都是利用了寬松相等條件下,JS 里的一些特殊表現(xiàn)來實現(xiàn)的,放在 === 這種嚴(yán)格相等的條件下就不能夠滿足,因為嚴(yán)格相等的條件下不會對兩個操作數(shù)做任何處理,直接比較它們值的大小,這樣上面的做法就不能成功。

          但是這種做法給我們提供了很好的思路,在處理類似的問題的時候,就可以從 JS 獲取一個變量執(zhí)行過程中出發(fā),來進行思考。那么接下來,如果題目中的寬松相等換成了嚴(yán)格相等,這樣的例子還存在么?

          if (a === 1 && a === 2 && a === 3) {
            console.log('Hello World!');
          }

          答案是顯然的,這一次當(dāng)然不能用 hack 對象或者 Proxy 的 toString 或者 ValueOf 方法來做。從 JS 獲取變量的過程入手,理所當(dāng)然的立馬能想到的就是數(shù)據(jù)的 getter 和 setter 方法,通過這樣的 hack ,肯定也能達到題目的嚴(yán)格相等的要求。

          在 ES5 之后,Object 新增 defineProperty 方法,它會直接在一個對象上定義一個新屬性,或者修改一個對象的現(xiàn)有屬性,并返回這個對象,對于定義的這個對象有兩種描述它的狀態(tài),一種稱之為數(shù)據(jù)描述符,一種被稱為存取描述符,分別舉一個例子:

          var a = {}
          Object.defineProperty(a, 'value', {
            enumerable: false,
            configurable: false,
            writable: false,
            value: "static"
          })

          這四個數(shù)據(jù)描述服分別作用是 enumerable 判斷是否可以枚舉,configurable 判斷當(dāng)前屬性是否之后再被更改描述符,writable 判斷是否可以繼續(xù)賦值,value 判斷這個結(jié)果的值。

          經(jīng)過這樣的操作之后,a 對象下就有了 value 這個 key ,他被賦予不可繼續(xù)賦值,不可繼續(xù)配置,不能被枚舉,值為 ‘static’,我們可以通過 a.value 拿到這里的 ‘static’,但是不能繼續(xù)通過 a.value = 'relative' 來繼續(xù)賦值。

          同樣的,設(shè)置存取描述符也是四個屬性:

          var a = { i: 1 }
          Object.defineProperty(a, 'value', {
            enumerable: false,
            configurable: false,
            get() { return a.i }
            set() { a.i++ }
          })

          這里設(shè)置時就沒有配置 writable 和 value 屬性,轉(zhuǎn)而配置了 get 和 set 方法,在這兩種配置中,get set 方法和 writable value 是不能共存的,否則就會拋出異常。類似上面這樣的設(shè)置,當(dāng)我們訪問 a.value 時就會調(diào)用 get 方法,當(dāng)我們通過 a.value = 'test' 時,就會執(zhí)行 set 方法。

          所以回歸到題目中,當(dāng)我們訪問一個被設(shè)置了存取描述符的元素時,如果在 get 方法里面做一些操作,就能巧妙的使得最終的結(jié)果達到預(yù)期:

          var i = 1
          Object.defineProperty(window, 'a', {
            get() { return i++ }
          })

          if (a === 1 && a === 2 && a === 3) {
            console.log('Hello World!');
          }

          同時,這種劫持 getter 和 setter 的方法本質(zhì)上是執(zhí)行了一個函數(shù),內(nèi)部除了用自增變量,還可以有更多的方法:

          const value = function* () {
            let i = 1
            while(true) yield i++
          }()

          Object.defineProperty(window, 'a', {
            get() {
              return value.next().value
            }
          })

          if (a === 1 && a === 2 && a === 3) {
            console.log('Hello World!');
          }

          總結(jié)

          對于嚴(yán)格相等的情況,一般來說只能通過劫持?jǐn)?shù)據(jù)的 getter 來進行操作,但是里面具體操作的方法在上面列舉的就有很多。

          對于寬松相等的情況,除了劫持 getter 以外,因為寬松相等 JS 引擎的緣故,還能用 Object , Proxy 對象的 valueOf 和 toString 方法達到目的。

          當(dāng)然,在 stackoverflow 中有人提出了另一種做法,在 a 變量的前后用不同的字符達到目的,原理就在于某些字符在肉眼條件下是不可見的,所以雖然看起來都是 a ,但變量實際上的不同的,也能達到題目的要求,不過這就不在本文的討論范圍之內(nèi)了。

          參考資料

          [1]

          can-a-1-a-2-a-3-ever-evaluate-to-true: https://stackoverflow.com/questions/48270127/can-a-1-a-2-a-3-ever-evaluate-to-true


          來源:https://zhangzhao.name/posts/make-a-1-a-2-a-3-evaluate-true/

          -End-

          最近有一些小伙伴,讓我?guī)兔φ乙恍?nbsp;面試題 資料,于是我翻遍了收藏的 5T 資料后,匯總整理出來,可以說是程序員面試必備!所有資料都整理到網(wǎng)盤了,歡迎下載!

          點擊??卡片,關(guān)注后回復(fù)【面試題】即可獲取

          瀏覽 52
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲AV无码国产成人久久 | 成人黄色性爱视频 | 久久大香视频 | а√中文在线资源库 | 久久99国产精品久久久久久久久 |