【跟着子迟品 underscore】JavaScript 中如何判断两个元素是否 "相同"
Why underscore
最近开始看 underscore.js 源码,并将 underscore.js 源码解读 放在了我的 2016 计划中。
阅读一些著名框架类库的源码,就好像和一个个大师对话,你会学到很多。为什么是 underscore?最主要的原因是 underscore 简短精悍(约 1.5k 行),封装了 100 多个有用的方法,耦合度低,非常适合逐个方法阅读,适合楼主这样的 JavaScript 初学者。从中,你不仅可以学到用 void 0 代替 undefined 避免 undefined 被重写等一些小技巧 ,也可以学到变量类型判断、函数节流&函数去抖等常用的方法,还可以学到很多浏览器兼容的 hack,更可以学到作者的整体设计思路以及 API 设计的原理(向后兼容)。
之后楼主会写一系列的文章跟大家分享在源码阅读中学习到的知识。
- underscore-1.8.3 源码解读项目地址 https://github.com/hanzichi/underscore-analysis
- underscore-1.8.3 源码全文注释 https://github.com/hanzichi/underscore-analysis/blob/master/underscore-1.8.3.js/underscore-1.8.3-analysis.js
- underscore-1.8.3 源码解读系列文章 https://github.com/hanzichi/underscore-analysis/issues
欢迎围观~ (如果有兴趣,欢迎 star & watch~)您的关注是楼主继续写作的动力
_.isEqual
本文跟大家聊聊 JavaScript 中如何判断两个参数 "相同",即 underscore 源码中的 _.isEqual 方法。这个方法可以说是 underscore 源码中实现最复杂的方法(用了百来行),几乎没有之一。
那么,我说的 "相同" 到底是什么意思?举个栗子,1
和 new 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 中如何判断两个元素是否 "相同"的更多相关文章
- 【跟着子迟品 underscore】常用类型判断以及一些有用的工具方法
Why underscore 最近开始看 underscore.js 源码,并将 underscore.js 源码解读 放在了我的 2016 计划中. 阅读一些著名框架类库的源码,就好像和一个个大师对 ...
- 【跟着子迟品 underscore】如何优雅地写一个『在数组中寻找指定元素』的方法
Why underscore (觉得这部分眼熟的可以直接跳到下一段了...) 最近开始看 underscore.js 源码,并将 underscore.js 源码解读 放在了我的 2016 计划中. ...
- 【跟着子迟品 underscore】for ... in 存在的浏览器兼容问题你造吗
Why underscore 最近开始看 underscore.js 源码,并将 underscore.js 源码解读 放在了我的 2016 计划中. 阅读一些著名框架类库的源码,就好像和一个个大师对 ...
- 【跟着子迟品 underscore】JavaScript 数组展开以及重要的内部方法 flatten
Why underscore (觉得这一段眼熟的童鞋可以直接跳到正文了...) 最近开始看 underscore.js 源码,并将 underscore.js 源码解读 放在了我的 2016 计划中. ...
- 【跟着子迟品 underscore】Array Functions 相关源码拾遗 & 小结
Why underscore 最近开始看 underscore.js 源码,并将 underscore.js 源码解读 放在了我的 2016 计划中. 阅读一些著名框架类库的源码,就好像和一个个大师对 ...
- 【跟着子迟品 underscore】Object Functions 相关源码拾遗 & 小结
Why underscore 最近开始看 underscore.js 源码,并将 underscore.js 源码解读 放在了我的 2016 计划中. 阅读一些著名框架类库的源码,就好像和一个个大师对 ...
- 【跟着子迟品underscore】从用 `void 0` 代替 `undefined` 说起
Why underscore 最近开始看 underscore源码,并将 underscore源码解读 放在了我的 2016计划 中. 阅读一些著名框架类库的源码,就好像和一个个大师对话,你会学到很多 ...
- javascript中怎么判断两个数据类型相等
在 JavaScript 中,判断一个变量的类型尝尝会用 typeof 运算符,在使用 typeof 运算符时采用引用类型存储值会出现一个问题,无论引用的是什么类型的对象,它都返回 "obj ...
- JavaScript中如何判断两变量是否“相等”?
1 为什么要判断? 可能有些同学看到这个标题就会产生疑惑,为什么我们要判断JavaScript中的两个变量是否相等,JavaScript不是已经提供了双等号“==”以及三等号“===”给我们使用了吗? ...
随机推荐
- 一步一步HTML5粒子编辑器
写在前面 大家阅读此文之前,可以先看一篇MiloYip的文章:用JavaScript玩转游戏物理(一)运动学模拟与粒子系统,看完之后再看此文,更加容易理解. MiloYip使用的粒子是canvas中绘 ...
- C语言中把数字转换为字符串 【转】
在将各种类型的数据构造成字符串时,sprintf 的强大功能很少会让你失望.由于sprintf 跟printf 在用法上几乎一样,只是打印的目的地不同而已,前者打印到字符串中,后者则直接在命令行上输出 ...
- javascript每天一题
请选择结果为真的表达式:()A.null instanceof ObjectB.null === undefinedC.null == undefinedD.NaN == NaN 答案在下面 选择C ...
- Android开发学习——SQLite数据库与单元测试
SQLite数据库 轻量级关系型数据库 创建数据库需要使用的api:SQLiteOpenHelper public class Myopenhelper extends SQLiteOpenHelp ...
- Android Json处理框架
1.Android 中的Json解析工具fastjson .序列化.反序列化 2.Android Gson的使用总结 3.Android-JSONTool 一个简易的Json框架类,小到只有一个类 有 ...
- Android项目实战(二十六):蓝牙连接硬件设备开发规范流程
前言: 最近接触蓝牙开发,主要是通过蓝牙连接获取传感器硬件设备的数据,并进行处理. 网上学习一番,现整理出一套比较标准的 操作流程代码. 如果大家看得懂,将来只需要改下 硬件设备的MAC码 和 改下对 ...
- (三)Maven仓库介绍与本地仓库配置
1.Maven本地仓库/远程仓库的基本介绍 示意图: 本地仓库是指存在于我们本机的仓库,在我们加入依赖时候,首先会跑到我们的本地仓库去找,如果找不到则会跑到远程仓库中去找.对于依赖的包大家可以从这个地 ...
- Bitset<>用于unordered container时的默认hash函数
自从c++11起,bitset用于unordered container,将会提供默认的hash函数. 在gcc中,相关代码如下: // DR 1182. /// std::hash speciali ...
- 我的第一个React Native App
我用了三天时间实现了一个相对比较完整的React Native 新闻发布类型的示例.应用做得很简单,但大多React Native的组件都有用到,今天做一个分享(由于我电脑是Windows系统,所以只 ...
- Play Framework 完整实现一个APP(十二)
1.定制CRUD管理页面 > play crud:ov --layout 替换生成文件内容 app/views/CRUD/layout.html #{extends 'admin.html' / ...