Underscore source code interpretation: How to judge whether two elements are "same" in JS

Why underscore

Recently I started to look at the source code of underscore.js and put the interpretation of the source code of underscore.js in my 2016 plan.

Reading the source code of some famous framework libraries is like talking to a master, you will learn a lot. Why is underscore? The main reason is that underscore is short and concise (about 1.5k lines), encapsulating more than 100 useful methods, low coupling, very suitable for reading method by method, suitable for JavaScript beginners like the original poster. From it, you can not only learn some tricks such as using void 0 instead of undefined to avoid undefined being rewritten, but also common methods such as variable type judgment, function throttling & function debounce, and many browser compatibility. The hack, you can also learn the author's overall design ideas and the principles of API design (backward compatibility).

Later, the host will write a series of articles to share with you the knowledge learned in source code reading.

Welcome to watch~ (If you are interested, welcome star & watch~) Your attention is the motivation for the host to continue writing

_.isEqual

This article will talk to you about how to determine that two parameters are "same" in JavaScript, that is, the _.isEqual method in the underscore source code. This method can be said to be the most complicated method to implement in the underscore source code (it took more than a hundred lines), almost none.

So, what do I mean by "same"? For example, 1 and new Number(1) are considered equal, [1] and [1] are considered equal (although their references are not the same), of course, two objects with the same reference must be equal. .

So, how to design this _.isEqual function? We follow the underscore source code and look at its implementation step by step. In the following text, it is assumed that the two parameters to be compared are a and b.

First of all, we judge that a === b is true. There are two cases. One is that a and b are basic types, then the values ​​of the two basic types are the same, and the other is two reference types, so they are reference types. The references are the same. So if a === b is true, does it mean that a and b are equal? In fact, 99% of the cases are like this, you have to consider the special case of 0 and -0, 0 === -0 is true, and 0 and -0 are considered unequal, as for the reason, you can refer to http:// wiki.ecmascript.org/doku.php?id=harmony:egal .

This part of the code can be expressed like this:


// 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).

// a === b 时

// 需要注意 `0 === -0` 这个 special case

// 0 和 -0 不相同

// 至于原因可以参考上面的链接

if (a === b) return a !== 0 || 1 / a === 1 / b;

The next situation is the situation of a !== b.

If one of a and b is null or undefined, then it can be judged specially and there is no need to continue the comparison. Source code implementation:


// A strict comparison is necessary because `null == undefined`.

// 如果 a 和 b 有一个为 null(或者 undefined)

// 判断 a === b

if (a == null || b == null) return a === b;

I personally feel that what is written here is a bit redundant, because according to the above judgment filtering, a === b must return false.

Ok, let's continue. Next, we can first judge based on the types of a and b. If the types are not the same, then there is no need to continue to judge. How to get the variable type? That's right, the magic Object.prototype.toString.call!

If the types are RegExp and String, we can convert a and b into strings respectively for comparison (if String is already a string), for example:


var a = /a/;

var b = new RegExp("a");

console.log(_.isEqual(a, b));  // => true

In fact, it is judged like this inside underscore:


var a = /a/;

var b = new RegExp("a");

var _a = '' + a; // => /a/

var _b = '' + b; // => /a/

console.log(_a === _b); // => true

What about the Number type? Here is another special case, which is NaN! It is stipulated here that NaN is only the same as NaN and unequal with other Number types. Here we convert all reference types to basic types, see the following code:


var a = new Number(1);

console.log(+a); // 1

That's right, just add a + and it's solved. The others are not difficult to understand, they are all in the comments.


// `NaN`s are equivalent, but non-reflexive.

// Object(NaN) is equivalent to NaN

// 如果 +a !== +a

// 那么 a 就是 NaN

// 判断 b 是否也是 NaN 即可

if (+a !== +a) return +b !== +b;

// An `egal` comparison is performed for other numeric values.

// 排除了 NaN 干扰

// 还要考虑 0 的干扰

// 用 +a 将 Number() 形式转为基本类型

// 如果 a 为 0,判断 1 / +a === 1 / b

// 否则判断 +a === +b

return +a === 0 ? 1 / +a === 1 / b : +a === +b;

// 如果 a 为 Number 类型

// 要注意 NaN 这个 special number

// NaN 和 NaN 被认为 equal

Next we look at the Date and Boolean types. Similar to the Number type, they can also be converted to basic types of numbers with +! Look at the following code:

var a = new Date();

var b = true;

var c = new Boolean(false);

console.log(+a); // 1464180857222

console.log(+b); // 1

console.log(+c); // 0

It’s very simple. In fact, +new Date() (or can also be written as +new Date) gets the current time and the number of milliseconds at 0:00 on January 1, 1970. Maybe you’ve heard of timestamps, but actually time The stamp is this data divided by 1000, which is the number of seconds. When using canvas for animation, I often use +new Date as a timestamp.

so, if both a and b are Date type or Boolean type, we can use +a === +b to judge whether it is equal.

The program continues, and we continue to look at it. There seem to be two important types that have not been judged? That's right, Array and Object! Underscore uses a recursive method for comparison.

Let's give a chestnut, for example, it is more intuitive.

Suppose a and b are as follows:


var a = {name: "hanzichi", loveCity: [{cityName: "hangzhou", province: "zhenjiang"}], age: 30};

var b = {name: "hanzichi", loveCity: [{cityName: "hangzhou", province: "zhenjiang"}], age: 25};

First, a and b are objects, and we can compare their key-value pairs respectively. If there is a key-value pair that is different (or if there is a key-value pair a and b), then a and b are unequal. What if it is an array? Then one element by one is more. Because the array may nest objects, and the value of the object may be an array, recursion is used here.

Still taking the above example, we can split it into three comparisons, and compare whether the values ​​of the three keys are the same. For the value of the key loveCity, because its value is an array again, we pass this value into the comparison function, and use the result of this comparison to determine the final comparison result. This is how recursion is. Big things can be broken down into smaller ones, and the smaller ones can be summarized to get the bigger ones.

Finally, the code location is given. For the source code of the _.isEqual method, you can refer to here.

Guess you like

Origin blog.51cto.com/15080022/2588317