判斷JavaScript中的兩個(gè)變量是否相等的操作符
1、為什么要判斷?
可能有些同學(xué)看到這個(gè)標(biāo)題就會(huì)產(chǎn)生疑惑,為什么我們要判斷JavaScript中的兩個(gè)變量是否相等,JavaScript不是已經(jīng)提供了雙等號(hào)“==”以及三等號(hào)“===”給我們使用了嗎?
其實(shí),JavaScript雖然給我們提供了相等運(yùn)算符,但是還是存在一些缺陷,這些缺陷不符合我們的思維習(xí)慣,有可能在使用的時(shí)候得到一些意外的結(jié)果。為了避免這種情況的出現(xiàn),我們需要自己函數(shù)來實(shí)現(xiàn)JavaScript變量之間的對(duì)比。
2、JavaScript等號(hào)運(yùn)算符存在哪些缺陷?
2.1 0與-0
在JavaScript中:
0 === 0
//true
+0 === -0
//true
相等運(yùn)算符認(rèn)為+0和-0是相等的,但是我們應(yīng)當(dāng)認(rèn)為兩者是不等的,具體原因源碼中給出了一個(gè)鏈接:Harmony egal proposal.
2.2 null和undefined
在JavaScript中:
null == undefined
//true
null === undefined
//false
我們應(yīng)當(dāng)認(rèn)為null不等于undefined,所以在比較null和undefined時(shí),應(yīng)當(dāng)返回false。
2.3 NaN
前文有說過,NaN是一個(gè)特殊的值,它是JavaScript中唯一一個(gè)自身不等于自身的值。
NaN == NaN
//false
NaN === NaN
//false
但是我們?cè)趯?duì)比兩個(gè)NaN時(shí),我們應(yīng)當(dāng)認(rèn)為它們是相等的。
2.4 數(shù)組之間的對(duì)比
由于在JavaScript中,數(shù)組是一個(gè)對(duì)象,所以如果兩個(gè)變量不是引用的同一個(gè)數(shù)組的話,即使兩個(gè)數(shù)組一模一樣也不會(huì)返回true。
var a = [];
//undefined
var b = [];
//undefined
a=== b
//false
a==b
//false
但是我們應(yīng)當(dāng)認(rèn)為,兩個(gè)元素位置、順序以及值相同的數(shù)組是相等的。
2.5 對(duì)象之間的對(duì)比
凡是涉及到對(duì)象的變量,只要不是引用同一個(gè)對(duì)象,都會(huì)被認(rèn)為不相等。我們需要做出一些改變,兩個(gè)完全一致的對(duì)象應(yīng)當(dāng)被認(rèn)為是相等的。
var a = {};
//undefined
var b = {};
//undefined
a == b
//false
a === b
//false
這種情況在所有JavaScript內(nèi)置對(duì)象中也適用,比如我們應(yīng)當(dāng)認(rèn)為兩個(gè)一樣的RegExp對(duì)象是相等的。
2.6 基本數(shù)據(jù)類型與包裝數(shù)據(jù)類型之間的對(duì)比
在JavaScript中,數(shù)值2和Number對(duì)象2是不嚴(yán)格相等的:
2 == new Number(2);
//true
2 === new Number(2);
//false
但是我們?cè)趯?duì)比2和new Number(2)時(shí)應(yīng)當(dāng)認(rèn)為兩者相等。
3 underscore的實(shí)現(xiàn)方法
我們實(shí)現(xiàn)的方法當(dāng)然還是依賴于JavaScript相等運(yùn)算符的,只不過針對(duì)特例需要有特定的處理。我們?cè)诒容^之前,首先應(yīng)該做的就是處理特殊情況。
underscore的代碼中,沒有直接將邏輯寫在_.isEqual方法中,而是定義了兩個(gè)私有方法:eq和deepEq。在GitHub用戶@hanzichi的repo中,我們可以看到1.8.3版本的underscore中并沒有deepEq方法,為什么后來添加了呢?這是因?yàn)閡nderscore的作者把一些特例的處理提取了出來,放到了eq方法中,而更加復(fù)雜的對(duì)象之間的對(duì)比被放到了deepEq中(同時(shí)使得deepEq方法更加便于遞歸調(diào)用)。這樣的做法使得代碼邏輯更加鮮明,方法的功能也更加單一明確,維護(hù)代碼更加簡(jiǎn)潔快速。
eq方法的源代碼:
var eq = function (a, b, aStack, bStack) {
// Identical objects are equal. `0 === -0`, but they aren't identical.
// See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal).
//除了0 === -0這個(gè)特例之外,其余所有a === b的例子都代表它們相等。
//應(yīng)當(dāng)判斷0 !== -0,但是JavaScript中0 === -0。
//下面這行代碼就是為了解決這個(gè)問題。
//當(dāng)a !== 0或者1/a === 1/b時(shí)返回true,一旦a === 0并且1/a !== 1/b就返回false。
//而a === 0且1/a !== 1/b就代表a,b有一個(gè)為0,有一個(gè)為-0。
if (a === b) return a !== 0 || 1 / a === 1 / b;
//一旦a、b不嚴(yán)格相等,就進(jìn)入后續(xù)檢測(cè)。
//a == b成立但是a === b不成立的例子中需要排除null和undefined,其余例子需要后續(xù)判斷。
// `null` or `undefined` only equal to itself (strict comparison).
//一旦a或者b中有一個(gè)為null就代表另一個(gè)為undefined,這種情況可以直接排除。
if (a == null || b == null) return false;
// `NaN`s are equivalent, but non-reflexive.
//自身不等于自身的情況,一旦a,b都為NaN,則可以返回true。
if (a !== a) return b !== b;
// Exhaust primitive checks
//如果a,b都不為JavaScript對(duì)象,那么經(jīng)過以上監(jiān)測(cè)之后還不嚴(yán)格相等的話就可以直接斷定a不等于b。
var type = typeof a;
if (type !== 'function' && type !== 'object' && typeof b != 'object') return false;
//如果a,b是JavaScript對(duì)象,還需要做后續(xù)深入的判斷。
return deepEq(a, b, aStack, bStack);
};
對(duì)于源碼的解讀我已經(jīng)作為注釋寫在了源碼中。 那么根據(jù)源碼,可以將其邏輯抽象出來:
deepEq的源碼:
var deepEq = function (a, b, aStack, bStack) {
// Unwrap any wrapped objects.
//如果a,b是_的一個(gè)實(shí)例的話,需要先把他們解包出來再進(jìn)行比較。
if (a instanceof _) a = a._wrapped;
if (b instanceof _) b = b._wrapped;
// Compare `[[Class]]` names.
//先根據(jù)a,b的Class字符串進(jìn)行比較,如果兩個(gè)對(duì)象的Class字符串都不一樣,
//那么直接可以認(rèn)為兩者不相等。
var className = toString.call(a);
if (className !== toString.call(b)) return false;
//如果兩者的Class字符串相等,再進(jìn)一步進(jìn)行比較。
//優(yōu)先檢測(cè)內(nèi)置對(duì)象之間的比較,非內(nèi)置對(duì)象再往后檢測(cè)。
switch (className) {
// Strings, numbers, regular expressions, dates, and booleans are compared by value.
//如果a,b為正則表達(dá)式,那么轉(zhuǎn)化為字符串判斷是否相等即可。
case '[object RegExp]':
// RegExps are coerced to strings for comparison (Note: '' + /a/i === '/a/i')
case '[object String]':
// Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
// equivalent to `new String("5")`.
//如果a, b是字符串對(duì)象,那么轉(zhuǎn)化為字符串進(jìn)行比較。因?yàn)橐幌聝蓚€(gè)變量:
//var x = new String('12');
//var y = new String('12');
//x === y是false,x === y也是false,但是我們應(yīng)該認(rèn)為x與y是相等的。
//所以我們需要將其轉(zhuǎn)化為字符串進(jìn)行比較。
return '' + a === '' + b;
case '[object Number]':
//數(shù)字對(duì)象轉(zhuǎn)化為數(shù)字進(jìn)行比較,并且要考慮new Number(NaN) === new Number(NaN)應(yīng)該要成立的情況。
// `NaN`s are equivalent, but non-reflexive.
// Object(NaN) is equivalent to NaN.
if (+a !== +a) return +b !== +b;
// An `egal` comparison is performed for other numeric values.
//排除0 === -0 的情況。
return +a === 0 ? 1 / +a === 1 / b : +a === +b;
case '[object Date]':
//Date類型以及Boolean類型都可以轉(zhuǎn)換為number類型進(jìn)行比較。
//在變量前加一個(gè)加號(hào)“+”,可以強(qiáng)制轉(zhuǎn)換為數(shù)值型。
//在Date型變量前加一個(gè)加號(hào)“+”可以將Date轉(zhuǎn)化為毫秒形式;Boolean類型同上(轉(zhuǎn)換為0或者1)。
case '[object Boolean]':
// Coerce dates and booleans to numeric primitive values. Dates are compared by their
// millisecond representations. Note that invalid dates with millisecond representations
// of `NaN` are not equivalent.
return +a === +b;
case '[object Symbol]':
return SymbolProto.valueOf.call(a) === SymbolProto.valueOf.call(b);
}
var areArrays = className === '[object Array]';
//如果不是數(shù)組對(duì)象。
if (!areArrays) {
if (typeof a != 'object' || typeof b != 'object') return false;
// Objects with different constructors are not equivalent, but `Object`s or `Array`s
// from different frames are.
//比較兩個(gè)非數(shù)組對(duì)象的構(gòu)造函數(shù)。
var aCtor = a.constructor, bCtor = b.constructor;
if (aCtor !== bCtor && !(_.isFunction(aCtor) && aCtor instanceof aCtor &&
_.isFunction(bCtor) && bCtor instanceof bCtor)
&& ('constructor' in a && 'constructor' in b)) {
return false;
}
}
// Assume equality for cyclic structures. The algorithm for detecting cyclic
// structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
// Initializing stack of traversed objects.
// It's done here since we only need them for objects and arrays comparison.
//初次調(diào)用eq函數(shù)時(shí),aStack以及bStack均未被傳遞,在循環(huán)遞歸的時(shí)候,會(huì)被傳遞進(jìn)來。
//aStack和bStack存在的意義在于循環(huán)引用對(duì)象之間的比較。
aStack = aStack || [];
bStack = bStack || [];
var length = aStack.length;
while (length--) {
// Linear search. Performance is inversely proportional to the number of
// unique nested structures.
if (aStack[length] === a) return bStack[length] === b;
}
// Add the first object to the stack of traversed objects.
//初次調(diào)用eq函數(shù)時(shí),就把兩個(gè)參數(shù)放入到參數(shù)堆棧中去,保存起來方便遞歸調(diào)用時(shí)使用。
aStack.push(a);
bStack.push(b);
// Recursively compare objects and arrays.
//如果是數(shù)組對(duì)象。
if (areArrays) {
// Compare array lengths to determine if a deep comparison is necessary.
length = a.length;
//長(zhǎng)度不等,直接返回false認(rèn)定為數(shù)組不相等。
if (length !== b.length) return false;
// Deep compare the contents, ignoring non-numeric properties.
while (length--) {
//遞歸調(diào)用。
if (!eq(a[length], b[length], aStack, bStack)) return false;
}
} else {
// Deep compare objects.
//對(duì)比純對(duì)象。
var keys = _.keys(a), key;
length = keys.length;
// Ensure that both objects contain the same number of properties before comparing deep equality.
//對(duì)比屬性數(shù)量,如果數(shù)量不等,直接返回false。
if (_.keys(b).length !== length) return false;
while (length--) {
// Deep compare each member
key = keys[length];
if (!(_.has(b, key) && eq(a[key], b[key], aStack, bStack))) return false;
}
}
// Remove the first object from the stack of traversed objects.
//循環(huán)遞歸結(jié)束,把a(bǔ),b堆棧中的元素推出。
aStack.pop();
bStack.pop();
return true;
};
對(duì)于源碼的解讀我已經(jīng)作為注釋寫在了源碼中。 那么根據(jù)源碼,可以將其邏輯抽象出來:
1 使用Object.prototype.toString方法獲取兩參數(shù)類型,如果兩參數(shù)的原始數(shù)據(jù)類型都不同,那么可以認(rèn)為兩個(gè)參數(shù)不相等。
2 如果進(jìn)入了第二步,那么說明兩個(gè)參數(shù)的原始類型相同。針對(duì)獲取到的字符串進(jìn)行分類,如果是除Object和Array之外的類型,進(jìn)行處理。RegExp以及String對(duì)象轉(zhuǎn)化為字符串進(jìn)行比較。
Number類型的話,需要先使用+運(yùn)算符強(qiáng)制轉(zhuǎn)化為基本數(shù)據(jù)類型中的數(shù)值型,然后處理特例。比如NaN === NaN,0 !== -0.
Date以及Boolean對(duì)象轉(zhuǎn)化為數(shù)字類型進(jìn)行對(duì)比。(+運(yùn)算符強(qiáng)制轉(zhuǎn)換,Date轉(zhuǎn)化為13位的毫秒形式,Boolean轉(zhuǎn)化為0或1)
Symbol類型使用Symbol.prototype.valueOf獲取字符串,然后進(jìn)行對(duì)比(即認(rèn)為傳遞給Symbol函數(shù)相同字符串所獲取到的Symbol對(duì)象應(yīng)該相等)。3 經(jīng)過以上比較,所剩類型基本只剩Array和基本對(duì)象了。如果不是數(shù)組對(duì)象,那么構(gòu)造函數(shù)不同的對(duì)象可以被認(rèn)為是不相等的對(duì)象。
4 初始化對(duì)象棧aStack以及bStack,因?yàn)槌醮握{(diào)用deepEq函數(shù)時(shí)不會(huì)傳遞這兩個(gè)參數(shù),所以需要手動(dòng)初始化。因?yàn)橹蟊容^的數(shù)組對(duì)象以及基本對(duì)象需要用到對(duì)象棧,所以現(xiàn)在應(yīng)該把當(dāng)前的a,b推入到兩個(gè)棧中。
5 針對(duì)數(shù)組,先比較長(zhǎng)度,長(zhǎng)度不等則數(shù)組不等。長(zhǎng)度相等再遞歸調(diào)用deepGet比較數(shù)組的每一項(xiàng),有一項(xiàng)不等則返回false。
6 基本對(duì)象類型比較,先使用_.keys獲取對(duì)象的所有鍵。鍵數(shù)量不同的兩對(duì)象不同,如果鍵數(shù)目相等,再遞歸調(diào)用deepEq比較每一個(gè)鍵的屬性,有一個(gè)鍵值不等則返回false。
7 經(jīng)過所有檢測(cè)如果都沒有返回false的話,可以認(rèn)為兩參數(shù)相等,返回true。在返回之前會(huì)把棧中的數(shù)據(jù)推出一個(gè)。
4 underscore的精髓
4.1 將RegExp對(duì)象和String對(duì)象用相同方法處理
有同學(xué)可能會(huì)疑惑:/[a-z]/gi與/[a-z]ig/在意義上是一樣的,但是轉(zhuǎn)化為字符串之后比較會(huì)不會(huì)是不相等的?
這是一個(gè)非常好的問題,同時(shí)也是underscore處理的巧妙之所在。在JavaScript中,RegExp對(duì)象重寫了toString方法,所以在強(qiáng)制將RegExp對(duì)象轉(zhuǎn)化為字符串時(shí),flags會(huì)按規(guī)定順序排列,所以將之前兩個(gè)RegExp對(duì)象轉(zhuǎn)化為字符串,都會(huì)得到/[a-z]/gi。這就是underscore可以放心大膽的將RegExp對(duì)象轉(zhuǎn)化為字符串處理的原因。
4.2 Date對(duì)象和Boolean對(duì)象使用相同方法處理
underscore選擇將Date對(duì)象和Boolean對(duì)象都轉(zhuǎn)化為數(shù)值進(jìn)行處理,這避免了紛繁復(fù)雜的類型轉(zhuǎn)換,簡(jiǎn)單粗暴。而且作者沒有使用強(qiáng)制轉(zhuǎn)換方法進(jìn)行轉(zhuǎn)換,而是只使用了一個(gè)“+”符號(hào),就強(qiáng)制將Date對(duì)象和Boolean對(duì)象轉(zhuǎn)換成了數(shù)值型數(shù)據(jù)。
4.3 使用對(duì)象棧保存當(dāng)前比較對(duì)象的上下文
很多童鞋在閱讀源碼時(shí),可能會(huì)很疑惑aStack以及bStack的作用在哪里。aStack和bStack用于保存當(dāng)前比較對(duì)象的上下文,這使得我們?cè)诒容^某個(gè)對(duì)象的子屬性時(shí),還可以獲取到其自身。這樣做的好處就在于我們可以比較循環(huán)引用的對(duì)象。
var a = {
name: 'test'
};
a['test1'] = a;
var b = {
name: 'test'
};
b['test1'] = b;
_.isEqual(a, b);
//true
underscore使用aStack和bStack作比較的代碼:
aStack = aStack || [];
bStack = bStack || [];
var length = aStack.length;
while (length--) {
// Linear search. Performance is inversely proportional to the number of
// unique nested structures.
if (aStack[length] === a) return bStack[length] === b;
}
上面的測(cè)試代碼中,a、b對(duì)象的test1屬性都引用了它們自身,這樣的對(duì)象在比較時(shí)會(huì)消耗不必要的時(shí)間,因?yàn)橹灰猘和b的test1屬性都等于其某個(gè)父對(duì)象,那么可以認(rèn)為a和b相等,因?yàn)檫@個(gè)被遞歸的方法返回之后,還要繼續(xù)比較它們對(duì)應(yīng)的那個(gè)父對(duì)象,父對(duì)象相等,則引用的對(duì)象屬性必相等,這樣的處理方法節(jié)省了很多的時(shí)間,也提高了underscore的性能。
4.4 優(yōu)先級(jí)分明,有的放矢
underscore的處理具有很強(qiáng)的優(yōu)先級(jí),比如在比較數(shù)組對(duì)象時(shí),先比較數(shù)組的長(zhǎng)度,數(shù)組長(zhǎng)度不相同則數(shù)組必定不相等;比如在比較基本對(duì)象時(shí),優(yōu)先比較對(duì)象鍵的數(shù)目,鍵數(shù)目不等則對(duì)象必定不等;比如在比較兩個(gè)對(duì)象參數(shù)之前,優(yōu)先對(duì)比Object.prototype.toString返回的字符串,如果基本類型不同,那么兩個(gè)對(duì)象必定不相等。
這樣的主次分明的對(duì)比,大大提高了underscore的工作效率。所以說每一個(gè)小小的細(xì)節(jié),都可以體現(xiàn)出作者的處心積慮。閱讀源碼,能夠使我們學(xué)習(xí)到太多的東西。
5 underscore的缺陷之處
我們可以在其他方法中看到underscore對(duì)ES6中新特征的支持,比如_.is[Type]方法已經(jīng)支持檢測(cè)Map(_.isMap)和Set(_.isSet)等類型了。但是_.isEqual卻沒有對(duì)Set和Map結(jié)構(gòu)的支持。如果我們使用_.isEqual比較兩個(gè)Map或者兩個(gè)Set,總是會(huì)得到true的結(jié)果,因?yàn)樗鼈兛梢酝ㄟ^所有的檢測(cè)。
在underscore的官方GitHub repo上,我看到有同學(xué)已經(jīng)提交了PR添加了_.isEqual對(duì)Set和Map的支持。
我們可以看一下源碼:
var size = a.size;
// Ensure that both objects are of the same size before comparing deep equality.
if (b.size !== size) return false;
while (size--) {
// Deep compare the keys of each member, using SameValueZero (isEq) for the keys
if (!(isEq(a.keys().next().value, b.keys().next().value, aStack, bStack))) return false;
// If the objects are maps deep compare the values. Value equality does not use SameValueZero.
if (className === '[object Map]') {
if (!(eq(a.values().next().value, b.values().next().value, aStack, bStack))) return false;
}
}
可以看到其思路如下:
1 比較兩參數(shù)的長(zhǎng)度(或者說是鍵值對(duì)數(shù)),長(zhǎng)度不一者即為不等,返回false。
2 如果長(zhǎng)度相等,就逐一遞歸比較它們的每一項(xiàng),有任意一項(xiàng)不等者就返回false。
3 全部通過則可以認(rèn)為是相等的,返回true。
這段代碼有一個(gè)很巧妙的地方在于它沒有區(qū)分到底是Map對(duì)象還是Set對(duì)象,先直接使用a.keys().next().value以及b.keys().next().value獲取Set的元素值或者M(jìn)ap的鍵。后面再進(jìn)行類型判斷,如果是Map對(duì)象的話,再使用a.values().next().value以及b.values().next().value獲取Map的鍵值,Map對(duì)象還需要比較其鍵值是否相等。
個(gè)人認(rèn)為,這段代碼也有其局限性,因?yàn)镾et和Map可以認(rèn)為是一個(gè)數(shù)據(jù)集,這區(qū)別于數(shù)組對(duì)象。我們可以說[1,2,3]不等于[2,1,3],因?yàn)槠湎嗤氐奈恢貌煌坏俏艺J(rèn)為new Set([1,2,3])應(yīng)該認(rèn)為等于new Set([2,1,3]),因?yàn)镾et是無序的,它內(nèi)部的元素具有單一性。
獲取更多underscore源碼解讀:GitHub
好了這篇文章就介紹這么多,更多關(guān)于js相等操作符請(qǐng)看下面的相關(guān)文章。
上一篇:vue圖片上傳組件使用詳解
欄 目:JavaScript
本文標(biāo)題:判斷JavaScript中的兩個(gè)變量是否相等的操作符
本文地址:http://www.jygsgssxh.com/a1/JavaScript/9431.html
您可能感興趣的文章
- 04-02javascript潛力,javascript強(qiáng)大嗎
- 04-02javascript點(diǎn)線,點(diǎn)線的代碼
- 04-02javascript移出,js 移入移出
- 04-02javascript替換字符串,js字符串的替換
- 04-02包含javascript舍的詞條
- 04-02javascript匿名,js匿名方法
- 04-02javascript并行,深入理解并行編程 豆瓣
- 04-02javascript警報(bào),JavaScript警告
- 04-02javascript前身,javascript的前身
- 04-02javascript遮蓋,JavaScript遮蓋PC端頁面


閱讀排行
- 1C語言 while語句的用法詳解
- 2java 實(shí)現(xiàn)簡(jiǎn)單圣誕樹的示例代碼(圣誕
- 3利用C語言實(shí)現(xiàn)“百馬百擔(dān)”問題方法
- 4C語言中計(jì)算正弦的相關(guān)函數(shù)總結(jié)
- 5c語言計(jì)算三角形面積代碼
- 6什么是 WSH(腳本宿主)的詳細(xì)解釋
- 7C++ 中隨機(jī)函數(shù)random函數(shù)的使用方法
- 8正則表達(dá)式匹配各種特殊字符
- 9C語言十進(jìn)制轉(zhuǎn)二進(jìn)制代碼實(shí)例
- 10C語言查找數(shù)組里數(shù)字重復(fù)次數(shù)的方法
本欄相關(guān)
- 04-02javascript點(diǎn)線,點(diǎn)線的代碼
- 04-02javascript潛力,javascript強(qiáng)大嗎
- 04-02javascript替換字符串,js字符串的替換
- 04-02javascript移出,js 移入移出
- 04-02包含javascript舍的詞條
- 04-02javascript并行,深入理解并行編程 豆瓣
- 04-02javascript匿名,js匿名方法
- 04-02javascript警報(bào),JavaScript警告
- 04-02javascript遮蓋,JavaScript遮蓋PC端頁面
- 04-02javascript前身,javascript的前身
隨機(jī)閱讀
- 08-05DEDE織夢(mèng)data目錄下的sessions文件夾有什
- 01-10使用C語言求解撲克牌的順子及n個(gè)骰子
- 01-10delphi制作wav文件的方法
- 01-10C#中split用法實(shí)例總結(jié)
- 01-10SublimeText編譯C開發(fā)環(huán)境設(shè)置
- 08-05織夢(mèng)dedecms什么時(shí)候用欄目交叉功能?
- 08-05dedecms(織夢(mèng))副欄目數(shù)量限制代碼修改
- 01-11Mac OSX 打開原生自帶讀寫NTFS功能(圖文
- 04-02jquery與jsp,用jquery
- 01-11ajax實(shí)現(xiàn)頁面的局部加載


