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

【跟着子迟品 underscore】JavaScript 中如何判断两个元素是否 "相同"的更多相关文章

  1. 【跟着子迟品 underscore】常用类型判断以及一些有用的工具方法

    Why underscore 最近开始看 underscore.js 源码,并将 underscore.js 源码解读 放在了我的 2016 计划中. 阅读一些著名框架类库的源码,就好像和一个个大师对 ...

  2. 【跟着子迟品 underscore】如何优雅地写一个『在数组中寻找指定元素』的方法

    Why underscore (觉得这部分眼熟的可以直接跳到下一段了...) 最近开始看 underscore.js 源码,并将 underscore.js 源码解读 放在了我的 2016 计划中. ...

  3. 【跟着子迟品 underscore】for ... in 存在的浏览器兼容问题你造吗

    Why underscore 最近开始看 underscore.js 源码,并将 underscore.js 源码解读 放在了我的 2016 计划中. 阅读一些著名框架类库的源码,就好像和一个个大师对 ...

  4. 【跟着子迟品 underscore】JavaScript 数组展开以及重要的内部方法 flatten

    Why underscore (觉得这一段眼熟的童鞋可以直接跳到正文了...) 最近开始看 underscore.js 源码,并将 underscore.js 源码解读 放在了我的 2016 计划中. ...

  5. 【跟着子迟品 underscore】Array Functions 相关源码拾遗 & 小结

    Why underscore 最近开始看 underscore.js 源码,并将 underscore.js 源码解读 放在了我的 2016 计划中. 阅读一些著名框架类库的源码,就好像和一个个大师对 ...

  6. 【跟着子迟品 underscore】Object Functions 相关源码拾遗 & 小结

    Why underscore 最近开始看 underscore.js 源码,并将 underscore.js 源码解读 放在了我的 2016 计划中. 阅读一些著名框架类库的源码,就好像和一个个大师对 ...

  7. 【跟着子迟品underscore】从用 `void 0` 代替 `undefined` 说起

    Why underscore 最近开始看 underscore源码,并将 underscore源码解读 放在了我的 2016计划 中. 阅读一些著名框架类库的源码,就好像和一个个大师对话,你会学到很多 ...

  8. javascript中怎么判断两个数据类型相等

    在 JavaScript 中,判断一个变量的类型尝尝会用 typeof 运算符,在使用 typeof 运算符时采用引用类型存储值会出现一个问题,无论引用的是什么类型的对象,它都返回 "obj ...

  9. JavaScript中如何判断两变量是否“相等”?

    1 为什么要判断? 可能有些同学看到这个标题就会产生疑惑,为什么我们要判断JavaScript中的两个变量是否相等,JavaScript不是已经提供了双等号“==”以及三等号“===”给我们使用了吗? ...

随机推荐

  1. 把Sharepoint Desinger 工作流部署到生产环境

    下面是比较简单的方法,把Designer工作流从开发环境部署到生产环境. 在Sharepoint Desinger 2013 中点击需要部署的工作流. 点击保存,发布. 点Export to Visi ...

  2. MAC下的XMPP环境搭建

    实现即时通信有多种方式,下面讲的是Mac下使用XMPP来实现. XML Messages Presence Protocol 可扩展消息处理协议 简单讲就是基于XML语言的点对点即时通信协议  原理: ...

  3. 隐式启动判断是否有匹配的Intent

    一.PackageManager的resolveActivity public abstract ResolveInfo resolveActivity(Intent intent, int flag ...

  4. 又一个高性能轻量级的iOS模型框架YYModel

    前言 iOS的模型框架其实有很多了,去年研究过Mantle,也了解过JSONModel.MJExtension,最近的项目项目优化的时候,再次考虑,基于轻量级.高性能的考虑,最终选择了YYModel. ...

  5. C# Combobox 设置 value

    因为ComboxItem是Object对象,而控件自身没有Value属性.所以,需要自定义一个类,用其对象来存储Text,Value. public class ComboxItem    {     ...

  6. oracle查看对象信息

    1.查看某用户下所有对象的信息: SELECT owner, object_type, status, COUNT(*) count# FROM all_objects where owner='xx ...

  7. 穿越之旅之--android中如何执行java命令

    android的程序基于java开发,当我们接上调试器,执行adb shell,就可以执行linux命令,但是却并不能执行java命令. 那么在android的shell中是否就不能执行java程序了 ...

  8. android xml 布局错误

    最近重新安装了下android开发环境,发现在调整页面的时候 ,老是报以下错误,导致无法静态显示ui效果. Missing styles. Is the correct theme chosen fo ...

  9. ASP.NET Core AD 域登录

    在选择AD登录时,其实可以直接选择 Windows 授权,不过因为有些网站需要的是LDAP获取信息进行授权,而非直接依赖Web Server自带的Windows 授权功能. 当然如果使用的是Azure ...

  10. Release Management 安装 之 集成TFS

    集成TFS时需要在TFS服务器执行 tfssecurity /g+ "Team Foundation Service Accounts" n:ALM\rmtfsint ALLOW ...