原文: Will (a===1 && a===2 && a===3) (strict comparison) ever be true (in JavaScript)javascript
本文是JS經典問題(a == 1 && a==2 && a==3)(寬鬆相等)的擴展和解決方案html
咱們先簡單瞭解這道JS經典問題, 而後再解決它的擴展問題。java
內容概覽:git
若是你已經瞭解過這個問題而且知道如何解決這個JS謎題(是的,只是一個謎題,我並不想在生產代碼中看到這樣的用例) , 那你能夠直接跳到下一節,閱讀它的擴展問題。關於這個問題在reddit上有相關討論, 下面是我看到最有趣的評論github
"若是我在代碼庫中看到這樣的代碼,我可能就很絕望了" // 譯者注: 誰看到都會很絕望吧web
(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中關於==
的奇特的語法特性。bash
祕密就在於"寬鬆相等操做符 ==
"less
在JS中,寬鬆相等==
會先將左右兩兩邊的值轉化成相同的原始類型,而後再去比較他們是否相等。在轉化以後(==
一邊或兩邊都須要轉化),最後的相等匹配會像===
符號同樣去執行判斷。寬鬆相等是可逆的,對於任何值A與B,一般A == B
與B == A
具備相同的表現(除了轉換的順序不一樣)。能夠在這裏詳細深度地瞭解寬鬆匹配==
與嚴格匹配===
。
Javascript會如何強制轉換這個值呢?
在進行兩個值的比較時,執行了類型的強制轉換, 讓咱們先了解下內置的轉換函數。
ToPrimitive(input, PreferredType?)
複製代碼
可選參數PreferredType
能夠指定最終轉化的類型,它能夠是Number
類型或String
類型,這依賴於ToPrimitive()
方法執行的結果返回的是Number
類型或String
類型。
值的轉化過程以下
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)的值也能是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
描述符, 帶來了解決這個問題的辦法。
屬性描述符有兩種類型, 數據描述符和存取描述符。
數據描述符
強制鍵值 - 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
了。
《IVWEB 技術週刊》 震撼上線了,關注公衆號:IVWEB社區,每週定時推送優質文章。