-
Notifications
You must be signed in to change notification settings - Fork 642
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
JavaScript 中是如何比较两个元素是否 "相同" 的 #5
Comments
上面代码在chrome中报错,报错信息
我放在jshint里试一下没有报语法错误啊,还望指点一下 |
@2lei return 只能在function内部有效 |
楼上正解~ |
if (a == null || b == null) return a === b; 如果a == null && b == null , a === b 返回是true吧? |
@EdwardZZZ a,b 都为 null,那就直接执行 |
@hanzichi 对,看断片了,忘记前面的了 |
源码中有这么一段: |
@zhanyuzhang 看源码的确是这样,个人觉得比较两个函数是否相等没啥实用价值,可能设计的时候就忽略掉了 |
@zhanyuzhang 如果a或是b是Function的话,typeof出来的结果是‘function’,不是'object'。 |
f (a == null || b == null) return a === b; |
我的意思是 而且 eq 函数是有返回值的,返回布尔值。 |
能否问下源码中这一段是做什么的:
个人认为不会出现(aStack[length] === a)的情况啊? |
@aircloud 这段和上面一段我都看的云里雾里,也没有过分解读,递归一笔带过了 .. (其实是看不大懂) |
早上跑去 underscore 提了个 issue,结果也是觉得应该改成 false。 |
@ahonn 看到你的 pr 被 merge 了,看来我想的没错!Good job ! |
@Bubbfish underscore 中认为 |
@hanzichi 哦哦,这样啊,谢谢 |
看到楼主注释的源码 isEqual 函数有对 aCtor instanceof aCtor 的疑惑,这个应该是用来判断是否是 Object 构造函数,因为: Object instanceof Object // true |
这个可以比较两个数组相等吗 |
@mjylfz 当然可以啦~ |
看了isEqual部分的源码,有几个问题。
这个地方有三个判断条件,第一个是判断constructor属性值不等,没有争议,第二个是判断上面提到的不同frame间Object构造函数不同的情况,也没有争议( |
2.对于调用栈的那部分我不太理解
对于这部分代码,我理解的就是: |
@Klaus1995 当然有,对象的属性引用自身 |
@ahonn 原来是这样,那这么做就很有意义了,防止无限递归。谢谢解答 |
@Klaus1995 关于第一个问题,给你举个例子: var attrs = Object.create(null);
attrs.name = "Bob";
eq(attrs, {name: "Bob"}); // ??? 你会发现,在 underscore 中,一个有构造函数,一个没有构造函数,但是这两个对象是“相等”的,之所以判断 'constructor' in a 就是为了处理这种情况。 |
@mqyqingfeng 我都快忘光了 - - 看到你最近有在看,感谢帮忙解答 |
@hanzichi 楼主客气了,我从楼主的系列文章中受益颇多,也希望能帮忙解答一些问题~ |
@mqyqingfeng 所以underscore里面对‘equal’的定义是这样的: |
@Klaus1995 正是如此,不过要想建立一个没有构造函数的对象, Object.create({value: 1}) 之类的也是可以的,所以在 underscore 中 var a = Object.create({value: 1})
var b = Object.create({value: 2})
console.log(_.isEqual(a, b)) // true |
@mqyqingfeng 但是这样的话会有另外一个问题: |
@Klaus1995 是的,就是会有这个问题 |
@mqyqingfeng 所以这个算是bug还是什么?就算你那种例子也会有这个问题,道理是一样的。 |
@Klaus1995 是的,这就是个 bug,你可以到 underscore 去提一个 issue,不过在实际开发中,也不会遇到这样的问题吧~ |
@mqyqingfeng |
请教楼主一个问题。 // An 这部分代码一直不太理解。 |
@mqyqingfeng,你好,非常感谢你的回答,你的排版非常漂亮,而且回答得细致全面。 0和-0的区别我是知道的,但是在underscore的eq方法一开始就已经对此进行了判断了,也就是下面这段代码: // Identical objects are equal. 然后,在下面,又出现了这样的代码: |
@rookiePrgrmer 用于判断 Number(+0) 和 Number(-0) |
return +a === 0 ? 1 / +a === 1 / b : +a === +b; 上面当 +a === 0 时,为什么 此外,最新的 underscore 里面加入了 如下的判断: case '[object Symbol]':
return SymbolProto.valueOf.call(a) === SymbolProto.valueOf.call(b); 这里是否是一种判断失误 呢? 最后还有一部分就是上面提到的调用栈的问题: aStack = aStack || [];
bStack = bStack || [];
var length = aStack.length;
while (length--) { // 逐个比较其值 第一次时 length = 0 这里不会执行
// aStack[length] === a
// aStack[length] 这里难道不是空吗?不应该是 length - 1?
if (aStack[length] === a) return bStack[length] === b
}
aStack.push(a)
bStack.push(b) 谢谢解答^_^ |
@jyzwf 我查了下起源的 PR,在 jashkenas/underscore@1dd1133?diff=unified 并没有讲到更改的原因,我个人觉得即便改成 + ,也是没有什么影响的。 第二个问题 Symbol 表示独一无二的值,即便 Symbol(1) 和 Symbol(1) 也是不一样的,结果是应该 false 吧 第三个问题在 while(length--) 的时候,length 的值就已经减去了 1 |
@mqyqingfeng 谢谢你的回答, |
@hanzichi
|
@freedomme |
这个语句 +variable 会尝试将 $ node
> +'1'
1
> +'0x0a'
10
> +'1e10'
10000000000
> +'-2'
-2
> +'[]'
NaN
> +'-0'
-0
> |
关于两个stack。广度有限遍历,用stack缓存所有上层及同层已处理节点,对于当前节点a,如果a在缓存中,则a被循环嵌套,那么判断在同样位置b节点是否也被循环嵌套。存在嵌套必须return,不return会死循环。 |
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 源码中实现最复杂的方法(用了百来行),几乎没有之一。
那么,我说的 "相同" 到底是什么意思?举个栗子,
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。这部分代码可以这样表示:
接下去的情况,也就是
a !== b
的情况了。如果 a 和 b 中有一个是 null 或者 undefined,那么可以特判下,不用继续比较了。源码实现:
个人觉得这里写的有点多余,因为根据上面的判断过滤,a === b 肯定是返回 false 的。
ok,我们继续,接下来我们可以先根据 a 和 b 的类型来判断,如果类型不一样,那么就没必要继续判断了。如何获取变量类型?没错,就是神奇的
Object.prototype.toString.call
!如果类型是 RegExp 和 String,我们可以将 a 和 b 分别转为字符串进行比较(如果是 String 就已经是字符串了),举个栗子:
其实它在 underscore 内部是这样判断的:
如果是 Number 类型呢?这里又有个 special case,就是 NaN!这里规定,NaN 仅和 NaN 相同,与别的 Number 类型均 unequal。这里我们将引用类型均转为基本类型,看如下代码:
没错,加个
+
就解决了,其他的不难理解,都在注释里了。接下来我们看 Date 和 Boolean 两个类型。跟 Number 类型相似,它们也可以用
+
转化为基本类型的数字!看下面代码:非常简单,其实 +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 如下:
首先 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
The text was updated successfully, but these errors were encountered: