原文: Will (a===1 && a===2 && a===3) (strict comparison) ever be true (in JavaScript)
本文是JS經典問題(a == 1 && a==2 && a==3)(寬鬆相等)的擴充套件和解決方案
如何使用getter/setter描述符讓(a===1 && a===2 && a === 3)的值為true
我們先簡單瞭解這道JS經典問題, 然後再解決它的擴充套件問題。
內容概覽:
重溫(a==1&&a==2&&a==3)(寬鬆相等)問題
如果你已經瞭解過這個問題並且知道如何解決這個JS謎題(是的,只是一個謎題,我並不想在生產程式碼中看到這樣的用例) , 那你可以直接跳到下一節,閱讀它的擴充套件問題。關於這個問題在reddit上有相關討論, 下面是我看到最有趣的評論
"如果我在程式碼庫中看到這樣的程式碼,我可能就很絕望了" // 譯者注: 誰看到都會很絕望吧
(a==1 && a== 2 && a ==3 )問題
(a ==1 && a==2 && a==3) 的值可以是true嗎?
回答是肯定的, 具體可以看下面的程式碼
const a = { value : 0 };
a.valueOf = function() {
return this.value += 1;
};
console.log(a==1 && a==2 && a==3); //true
複製程式碼
通常, 在面試中問這類問題的目的並不是要求面試者記住這樣的答案,而是想要了解面試者在面對這道題目時,是如何思考的以及他們是否有了解過Javascript中關於==
的奇特的語法特性。
問題解析
祕密就在於"寬鬆相等操作符 ==
"
在JS中,寬鬆相等==
會先將左右兩兩邊的值轉化成相同的原始型別,然後再去比較他們是否相等。在轉化之後(==
一邊或兩邊都需要轉化),最後的相等匹配會像===
符號一樣去執行判斷。寬鬆相等是可逆的,對於任何值A與B,通常A == B
與B == A
具有相同的表現(除了轉換的順序不同)。可以在這裡詳細深度地瞭解寬鬆匹配==
與嚴格匹配===
。
Javascript會如何強制轉換這個值呢?
在進行兩個值的比較時,執行了型別的強制轉換, 讓我們先了解下內建的轉換函式。
ToPrimitive(input, PreferredType?)
複製程式碼
可選引數PreferredType
可以指定最終轉化的型別,它可以是Number
型別或String
型別,這依賴於ToPrimitive()
方法執行的結果返回的是Number
型別或String
型別。
值的轉化過程如下
- 如果輸入Input是基本型別, 就返回這個值
- 如果輸入變數是Object型別, 那麼呼叫input.valueOf(). 如果返回結果是基本型別,就返回這個指
- 如果都不是的話就呼叫input.toString(). 如果結果是基本型別, 就返回它
- 如果以上都不可以,就會丟擲一個型別錯誤
TypeError
, 表示轉化input變數到基本型別失敗。
如果PreferredType
是Number
, 那轉換演算法就會像上述說明的順序執行,如果是String
,步驟2和步驟3會交換順序。PreferredType
是一個預設值,如果不輸入的話,Date
型別會被當作String
型別處理,其他變數會當作Number
處理。預設的valueOf
返回this
,預設的toString()會返回型別資訊。
如上是操作符+
和==
呼叫toPrimitive()
的執行過程。
所以在上面的程式碼中, 如JS引擎所解析的,a == 1
, 1
是基本型別, JS引擎會嘗試將a
轉換成Number
型別,然後在上面的演算法中,a.valueOf
被呼叫並且返回1(自增1並且返回自己)。在a==2
和a==3
發生了同樣的型別轉換並增加自己的值。
(a === 1 && a === 2 && a === 3)(嚴格匹配) 問題
(a === 1 && a === 2 && a ===3)的值也能是true嗎?
當然也可以, 具體請看下面的程式碼
var value = 0; //window.value
Object.defineProperty(window, 'a', {
get: function() {
return this.value += 1;
}
});
console.log(a===1 && a===2 && a===3) // true
複製程式碼
問題解釋
從經典問題的解答中,我們瞭解到JS中的原始型別將不再滿足於上面的條件(嚴格相等沒有轉化的過程),所以我們需要通過一些方式去呼叫一個函式,並在這個函式中做我們想做的事情。但是執行函式往往需要在函式名字後引入()
。並且由於這裡不是寬鬆相等==
,valueOf
將不會被JS引擎呼叫。Emmm, 有點棘手。還好有Property
函式, 特別是getter
描述符, 帶來了解決這個問題的辦法。
什麼是屬性描述符(property descriptors)?
屬性描述符有兩種型別, 資料描述符和存取描述符。
-
資料描述符
強制鍵值 - value
可選鍵值
- configurable - enumable - writeable 複製程式碼
例子
{ value: 5, writable: true } 複製程式碼
-
存取描述符
強制鍵值 - get/set或都設定 可選鍵值 - confiturable - enumerable 例子
{ get: function () { return 5; }, enumerable: true } 複製程式碼
MDN上關於存取描述符的例子
// Example of an object property added
// with defineProperty with an accessor property descriptor
var bValue = 38;
Object.defineProperty(o, 'b', {
// Using shorthand method names (ES2015 feature).
// This is equivalent to:
// get: function() { return bValue; },
// set: function(newValue) { bValue = newValue; },
get() { return bValue; },
set(newValue) { bValue = newValue; },
enumerable: true,
configurable: true
});
o.b; // 38
// 'b' property exists in the o object and its value is 38
// The value of o.b is now always identical to bValue,
// unless o.b is redefined
複製程式碼
在問題的解決方案中, 我們使用Object.defineProperty
為物件定義了一個屬性。你可以在這裡深入瞭解Object.defineProperty
的語法與定義。 有趣的是,get
和 set
是可以通過"."操作符
呼叫的方法, 舉個例子, a
有一個具有getter
的b
屬性, 它可以像物件的其他屬性一樣去呼叫,類似於a.b
。這可以解決我們最初的問題, 我們需要呼叫一個無需()
的函式, 通過get
屬性, 我們可以呼叫一個函式並且不用在函式名後新增()
在上面提到的解決方案中, 我們在window物件上定義了一個具有getter的a
屬性, 所以a
可以在程式碼中直接被訪問到(全域性變數), 因此也可以直接獲得a的值。如果我們在其他物件上定義了屬性a
而不是window的話,例如object1, 我們就需要改變題目為object1.a===1 && object1.a===2 && object1.a===3
了。
Github Gist
參考
《IVWEB 技術週刊》 震撼上線了,關注公眾號:IVWEB社群,每週定時推送優質文章。