How can you tell if two variables are "equal" in JavaScript?

Source: Internet
Author: User
Tags array length new set


1 Why should you judge?


Some students may see this title will be puzzled, why we have to determine whether the two variables in JavaScript is equal, JavaScript is not already provided with the double equals "= =" and the three equals "= = =" To us use it?



In fact, JavaScript gives us the equality operator, but there are some flaws that do not conform to our thinking habits, and it is possible to get some unexpected results when we use them. To avoid this situation, we need our own function to implement the comparison between JavaScript variables.


What are the defects of the 2 JavaScript equals operator? 2.1 0 and-0


In javascript:


0 === 0
//true
+0 === -0
//true





The equality operators think that +0 and 0 are equal, but we should consider the two to be unequal, for specific reasons a link is given in the source code: Harmony egal proposal.


2.2 Null and undefined


In javascript:


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





We should consider null not equal to undefined, so we should return FALSE when comparing null and undefined.


2.3 NaN


As mentioned earlier, Nan is a special value, which is the only value in JavaScript that does not itself equal itself.


NaN == NaN
//false
NaN === NaN
//false  





But when we compare two Nan, we should consider them to be equal.


2.4 Comparison between arrays


Since the array is an object in JavaScript, if two variables are not the same array of references, even two arrays will not return true.


var a = [];
//undefined
var b = [];
//undefined
a=== b
//false
a==b
//false





However, we should assume that the two elements are equal in their position, order, and value of the same array.


2.5 Comparison between objects


Any variable that involves an object is considered unequal as long as it does not refer to the same object. We need to make some changes, and two fully consistent objects should be considered equal.


var a  = {};
//undefined
var b = {};
//undefined
a == b
//false
a === b
//false





This is also true for all JavaScript built-in objects, such as we should consider two identical regexp objects to be equal.


2.6 Comparison between the basic data type and the wrapper data type


In JavaScript, the value 2 and Number object 2 are not strictly equal:


2 == new Number(2);
//true
2 === new Number(2);
//false





But we should consider the two equal when comparing 2 and new number (2).


3 How to implement underscore


The methods we implement are, of course, dependent on the JavaScript equality operator, but specific processing is required for exceptions. Before we compare, the first thing we should do is to deal with special situations.



Underscore's code does not directly write logic in the _.isequal method, but instead defines two private methods: EQ and Deepeq. In the repo of GitHub user @hanzichi, we can see that there is no Deepeq method in 1.8.3 version of underscore, why did you add it later? This is because the author of underscore has extracted some special cases, put them into the EQ method, and the comparison between the more complex objects is put into the deepeq (and makes the Deepeq method more convenient for recursive invocation). This approach makes the logic of the code more vivid, the function of the method is more single and clear, the maintenance code is more concise and fast.



Source code for the EQ method:


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).
//Except for the special case of 0 = = - 0, all the other examples of a = = = B represent that they are equal.
//You should judge 0! = = - 0, but 0 = = = - 0 in JavaScript.
//The following line of code is to solve this problem.
//Returns true when a! = = 0 or 1 / a = = = 1 / B, and false once a = = = 0 and 1 / a! = = 1 / b.
//And a = = = 0 and 1 / a! = = 1 / b means a, B has one as 0 and one as - 0.
if (a === b) return a !== 0 || 1 / a === 1 / b;
//Once a and B are not strictly equal, they will enter the subsequent detection.
//In the case where a = = B is valid but a = = = B is not, null and undefined need to be excluded, and the rest need to be judged later.
// `null` or `undefined` only equal to itself (strict comparison).
//Once one of a or B is null, it means the other is undefined, which can be ruled out directly.
if (a == null || b == null) return false;
// `NaN`s are equivalent, but non-reflexive.
//If it is not equal to itself, once a and B are Nan, it can return true.
if (a !== a) return b !== b;
// Exhaust primitive checks
//If a and B are not JavaScript objects, then if they are not strictly equal after the above monitoring, it can be directly concluded that a is not equal to B.
var type = typeof a;
if (type !== ‘function‘ && type !== ‘object‘ && typeof b != ‘object‘) return false;
//If a and B are JavaScript objects, we need to make further judgments.
return deepEq(a, b, aStack, bStack);
}





The interpretation of the source code I have written as a comment in the source code. Then, according to the source code, it can be abstracted out of logic:






Source code for DEEPEQ:


var deepEq = function (a, b, aStack, bStack) {
// Unwrap any wrapped objects.
//If a and B are an instance of UU, they need to be unpacked first and then compared.
if (a instanceof _) a = a._wrapped;
if (b instanceof _) b = b._wrapped;
// Compare `[[Class]]` names.
//First, compare the class strings of a and B. if the class strings of both objects are different,
//Then it can be directly considered that the two are not equal.
var className = toString.call(a);
if (className !== toString.call(b)) return false;
//If the class strings of the two are equal, compare them further.
//The comparison between built-in objects is detected first, and non built-in objects are detected later.
switch (className) {
// Strings, numbers, regular expressions, dates, and booleans are compared by value.
//If a and B are regular expressions, they can be converted to strings to determine whether they are equal.
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")`.
//If a and B are string objects, they are converted to strings for comparison. Because of the following two variables:
//var x = new String(‘12‘);
//var y = new String(‘12‘);
//X = = = y is false, x = = = y is false, but we should think that X and y are equal.
//So we need to convert it to a string for comparison.
return ‘‘ + a === ‘‘ + b;
case ‘[object Number]‘:
//The number object is converted to a number for comparison, and the case where new number (Nan) = = new number (Nan) should hold should be considered.
// `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.
//Exclude 0 = = = - 0.
return +a === 0 ? 1 / +a === 1 / b : +a === +b;
case ‘[object Date]‘:
//Date type and boolean type can be converted to number type for comparison.
//Add a plus sign "+" before the variable to cast it to numeric type.
//Add a plus sign "+" before the date type variable to convert the date to millisecond form; the boolean type is the same as above (convert to 0 or 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]‘;
//If not an array object.
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.
//Compares the constructors of two non array objects.
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.
//When the EQ function is called for the first time, astack and bstack are not passed in, and will be passed in when the loop recurs.
//The significance of the existence of astack and bstack lies in the comparison between circular reference objects.
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.
//When the EQ function is called for the first time, two parameters are put into the parameter stack, which is convenient for recursive call.
aStack.push(a);
bStack.push(b);
// Recursively compare objects and arrays.
//If it's an array object.
if (areArrays) {
// Compare array lengths to determine if a deep comparison is necessary.
length = a.length;
//If the length is not equal, return false directly to confirm that the array is not equal.
if (length !== b.length) return false;
// Deep compare the contents, ignoring non-numeric properties.
while (length--) {
//Recursive call.
if (!eq(a[length], b[length], aStack, bStack)) return false;
}
} else {
// Deep compare objects.
//Compare pure objects.
var keys = _.keys(a), key;
length = keys.length;
// Ensure that both objects contain the same number of properties before comparing deep equality.
//Compare the number of attributes. If the number is not equal, return false directly.
if (_.keys(b).length !== length) return false;
while (length--) {
// Deep compar





The interpretation of the source code I have written as a comment in the source code. Then, according to the source code, it can be abstracted out of logic:


    • 1 uses the Object.prototype.toString method to get two parameter types, and if the original data types of the two parameters are different, you can consider the two parameters to be unequal.

    • 2 If you enter the second step, the original type of the two parameters is the same. Classifies the obtained string, if it is a type other than object and array, to be processed. The

      • regexp and string objects are converted to strings for comparison.
      • Number type, you need to first use the + operator to convert to a numeric type in the base data type, and then handle the special case. For example NaN = = = nan,0!== -0.
      • Date and Boolean objects are converted to numeric types for comparison. (+ operator cast, date converted to 13-bit millisecond form, Boolean converted to 0 or 1)
      • The symbol type uses Symbol.prototype.valueOf to get the string and then compares it (that is, the symbol object that is obtained by the same string that is passed to the symbol function should be equal).
    • 3 After comparison, the remaining types are basically only the array and the base object. If it is not an array object, then different objects of the constructor can be considered unequal objects.

    • 4 initializes the object stack astack and Bstack, because both parameters are not passed when the DEEPEQ function is first called, so manual initialization is required. Since the object stack is required for the comparison of the array object and the base object, it is now time to push the current, A/b into the two stacks.

    • 5 for arrays, first comparing lengths, and arrays of unequal lengths. Equal length recursively calls Deepget to compare each item of an array, and returns false for one item.

    • 6 Base Object Type comparison, first using _.keys to get all the keys for an object. Two objects with different number of keys are different, if the number of keys is equal, then recursive call Deepeq to compare the properties of each key, there is a key value not equal to return false.

    • 7 After all detection if none returns false, the two arguments can be considered equal and return true. The data in the stack is rolled out before it is returned.

4 The essence of underscore 4.1 handles the RegExp object and the string object in the same way


Some students may wonder:/[a-z]/giis the/[a-z]ig/same as in the sense, but the conversion to a string after the comparison will not be equal?



This is a very good question, but it is also the ingenious underscore. In JavaScript, the RegExp object overrides the ToString method, so when the RegExp object is forced to be converted to a string, flags are arranged in the specified order, so converting the previous two RegExp objects to a string is obtained/[a-z]/gi. This is why underscore can confidently convert regexp objects into string processing.


The 4.2 Date object and the Boolean object use the same method to handle


Underscore chooses to convert both date and Boolean objects to numeric values, which avoids the complexity of type conversions and is simply rude. And instead of casting using the cast method, the author uses only a "+" symbol to force the conversion of the Date object and the Boolean object into numeric data.


4.3 Using the object stack to save the context of the current comparison object


Many children's shoes when reading the source code, may be very puzzled astack and bstack role in where. Astack and Bstack are used to hold the context of the current comparison object, which allows us to get to itself when we compare the child properties of an object. The advantage of this is that we can compare the objects that are referenced in the loop.


var a = {
    name: ‘test‘
};
a[‘test1‘] = a;
var b = {
    name: ‘test‘
};
b[‘test1‘] = b;
_.isEqual(a, b);
//true





Underscore code to compare using Astack and 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;
}





In the test code above, the Test1 properties of the A and B objects all refer to themselves, so that the objects will consume unnecessary time when compared, because as long as the Test1 property of A and B equals one of its parent objects, then A and B can be considered equal, since the recursive method returns. Also, to continue to compare the parent object to which they correspond, the parent object is equal, and the referenced object property must be equal, which saves a lot of time and improves the performance of the underscore.


4.4 Clear priorities, targeted


Underscore processing has a strong priority, for example, when comparing array objects, the array length is compared, the array length is not the same, the array must be unequal, for example, when comparing the basic object, the number of key objects will be compared, the number of keys is equal, the object must be unequal, for example, before comparing two object parameters, Compare the string returned by Object.prototype.toString, and if the base type is different, then two objects must be unequal.



This kind of contrast of the primary and secondary, greatly improved the efficiency of underscore. Therefore, every small detail can reflect the author's deliberate. Reading the source code, can make us learn too much things.


5 Defects of the underscore


We can see in other ways underscore support for new features in ES6, such as_.is[Type]methods that have supported the detection of(_.isMap)types such as map and set (_.isSet). However_.isEqual, there is no support for the set and map structures. If we use_.isEquala comparison of two maps or two sets, we will always get the result of true because they can pass all the detections.



On underscore's official GitHub repo, I saw that some of my classmates had already submitted PR added_.isEqualsupport for set and map.



We can look at the source code:


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;
    }
}





You can see the following ideas:


    • 1 compares the length of two parameters (or the logarithm of a key value), which is unequal in length and returns false.
    • 2 If the lengths are equal, each of them is recursively compared, and any one of them will return false.
    • 3 All passes can be considered equal and return true.


One neat thing about this code is that it does not distinguish between a map object or a set object, directly usinga.keys().next().valueandb.keys().next().valuegetting set element values or map keys . After the type judgment, if it is a map object, and then usea.values().next().valueandb.values().next().valueget the map key value, the map object also needs to compare its key value is equal.



Personally, this code also has its limitations, because set and map can be thought of as a dataset, which differs from array objects. We can say [2,1,3] is not equal to [a], because the same elements are in different positions, but I think new set ([+]) should be considered equal to the new set ([2,1,3]), because Set is unordered, its internal elements are singular.



How can you tell if two variables are "equal" in JavaScript?


Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.