【underscore 源码解读】JavaScript 中怎样推断两个元素是不是 "雷同"

Why underscore

近来最先看 underscore.js 源码,并将 underscore.js 源码解读 放在了我的 2016 设想中。

浏览一些有名框架类库的源码,就好像和一个个巨匠对话,你会学到许多。为何是 underscore?最主要的缘由是 underscore 简短精干(约 1.5k 行),封装了 100 多个有效的要领,耦合度低,异常合适逐一要领浏览,合适楼主如许的 JavaScript 初学者。从中,你不仅能够学到用 void 0 替代 undefined 防止 undefined 被重写等一些小技能 ,也能够学到变量范例推断、函数撙节&函数去抖等经常使用的要领,还能够学到许多浏览器兼容的 hack,更能够学到作者的团体设想思绪以及 API 设想的道理(向后兼容)。

以后楼主会写一系列的文章跟人人分享在源码浏览中进修到的学问。

迎接围观~ (假如有兴致,迎接 star & watch~)您的关注是楼主继承写作的动力

_.isEqual

本文跟人人聊聊 JavaScript 中怎样推断两个参数 “雷同”,即 underscore 源码中的 _.isEqual 要领。这个要领能够说是 underscore 源码中完成最庞杂的要领(用了百来行),几乎没有之一。

那末,我说的 “雷同” 究竟是什么意思?举个栗子,1new Number(1) 被以为是 equal,[1][1] 被以为是 equal(只管它们的援用并不雷同),固然,两个援用雷同的对象肯定是 equal 的了。

那末,怎样设想这个 _.isEqual 函数呢?我们随着 underscore 源码,一步步来看它的完成。后文中均假定比较的两个参数为 a 和 b。

起首我们推断 a === b,为 true 的状况有两种,其一是 a 和 b 都是基础范例,那末就是两个基础范例的值雷同,其二就是两个援用范例,那末就是援用范例的援用雷同。那末假如 a === b 为 true,是不是就是说 a 和 b 是 equal 的呢?事实上,99% 的状况是如许的,还得斟酌 0 和 -0 这个 special case,0 === -0 为 true,而 0 和 -0 被以为是 unequal,至于缘由,能够参考 http://wiki.ecmascript.org/doku.php?id=harmony:egal

这部份代码能够如许示意:

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

接下去的状况,也就是 a !== b 的状况了。

假如 a 和 b 中有一个是 null 或许 undefined,那末能够特判下,不必继承比较了。源码完成:

// A strict comparison is necessary because `null == undefined`.
// 假如 a 和 b 有一个为 null(或许 undefined)
// 推断 a === b
if (a == null || b == null) return a === b;

个人以为这里写的有点过剩,由于依据上面的推断过滤,a === b 肯定是返回 false 的。

ok,我们继承,接下来我们能够先依据 a 和 b 的范例来推断,假如范例不一样,那末就没必要继承推断了。怎样猎取变量范例?没错,就是奇异的 Object.prototype.toString.call

假如范例是 RegExp 和 String,我们能够将 a 和 b 离别转为字符串举行比较(假如是 String 就已经是字符串了),举个栗子:

var a = /a/;
var b = new RegExp("a");

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

实在它在 underscore 内部是如许推断的:

var a = /a/;
var b = new RegExp("a");

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

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

假如是 Number 范例呢?这里又有个 special case,就是 NaN!这里划定,NaN 仅和 NaN 雷同,与别的 Number 范例均 unequal。这里我们将援用范例均转为基础范例,看以下代码:

var a = new Number(1);
console.log(+a); // 1

没错,加个 + 就处理了,其他的不难理解,都在解释里了。

// `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

接下来我们看 Date 和 Boolean 两个范例。跟 Number 范例类似,它们也能够用 + 转化为基础范例的数字!看下面代码:

var a = new Date();
var b = true;
var c = new Boolean(false);

console.log(+a); // 1464180857222
console.log(+b); // 1
console.log(+c); // 0

异常简朴,实在 +new Date() (或许也能够写成 +new Date)猎取的恰是当前时候和 1970 年 1 月 1 日 0 点的毫秒数(millisecond),能够你听说过时候戳,实在时候戳就是这个数据除以 1000,也就是秒数。在用 canvas 做动画时,我经经常使用 +new Date 来当时候戳。

so,假如 a 和 b 均是 Date 范例或许 Boolean 范例,我们能够用 +a === +b 来推断是不是 equal。

顺序接着走,我们接着看,好像另有两类主要的范例没有推断?没错,Array 和 Object!underscore 对此采纳递归要领展开来比较。

照样举个栗子吧,举例比较直观。

假定 a,b 以下:

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

起首 a,b 是对象,我们能够离别比较其键值对,假如有一个键值对差别(或许说一个键值对 a 和 b 有一个没有),则 a 和 b unequal。假如是数组呢?那就一个一个元素比较喽。由于数组能够嵌套对象,对象的 value 又多是数组,所以这里用了递归。

照样以上面的例子,我们能够把它拆成三次比较,离别比较三个 key 的 value 值是不是雷同。关于 loveCity 这个 key 的 value,由于其 value 又是个数组,所以我们将这个 value 传入比较函数,经由过程这个比较的效果,来推断末了的比较效果。递归就是如许,能够将大的东西,拆成一个个小的,依据小的效果,来汇总获得大的效果。

末了,给出代码位置。关于 _.isEqual 要领的源码,人人能够参考 https://github.com/hanzichi/underscore-analysis/blob/master/underscore-1.8.3.js/src/underscore-1.8.3.js#L1094-L1190

    原文作者:fish
    原文地址: https://segmentfault.com/a/1190000005347766
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞