-
Notifications
You must be signed in to change notification settings - Fork 4.7k
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专题之如何判断两个对象相等 #41
Comments
当初看underscore的eq源码看的真是头大! |
真是头大,还好楼主解释清楚,不知道大神们都是如何考虑到这么多的情况的 |
@liuxinqiong 这或许就是开源项目的好处,会不停有人提 issue 帮忙改进~ |
@zdliuccit Object.is 就比 === 多了两个判断,一个是 NaN,一个是 +0 和 -0,然后 +0 和 -0 还被你说成缺陷 😂 if (!Object.is) {
Object.is = function(x, y) {
// SameValue algorithm
if (x === y) { // Steps 1-5, 7-10
// Steps 6.b-6.e: +0 != -0
return x !== 0 || 1 / x === 1 / y;
} else {
// Step 6.a: NaN == NaN
return x !== x && y !== y;
}
};
} |
做为一个前端菜鸟,表示看不懂阿 |
@jimStyle88 哪里没有看懂?可以留言讨论呀~ |
我的技术水平水平问题,不过还是会经常逛逛你的github ,挺详细的。 |
循环引用的比较还是有点复杂的 |
if (a !== a) return b !== b; |
@jerrymark1 判断NAN |
@shouhe 比如a是 {'a':1} 然后执行{'a':1} !== {'a':1} 不是直接返回 b !== b 了,没有执行下去了啊 |
明白了 @shouhe ,谢谢 |
啊啊啊,循环引用好变态,看了好几遍~ |
@mqyqingfeng 是否有必要返回true |
其实我大部分情况下,都是用JSON.stringify来判断的 |
@HuangQiii 哈哈,够用就可以啦~ |
@zhangenming 如果问的是"是否有必要"的话,这个其实要看需求是什么样的…… 在现在的实现中,这两个结果都会返回 false,因为两个参数并不是相同的类型,无论是使用 typeof 还是 Object.prototype.toString 的结果,对于 1 和 New Number(1) 而言,虽然 typeof 的结果不同,但是 Object.prototype.toString 的结果确是一样的 |
@liujuntao123 这段是用来判断两个不是同一个构造函数的实例对象是不相等的,之所以 aCtor instanceof aCtor 是用来判断 Object 构造函数的,因为: Object instanceof Object // true 所以如果 aCtor 是函数,并且 aCtor instanceof aCtor 就说明 aCtor 是 Object 函数 |
@liujuntao123 感谢指出哈~ 并不是故意升级为全局变量的,在写这一块的时候,确实是第一次使用 length 变量,理应使用 var 声明,不过从最终的源码中看的话,因为之前已经声明了 length 变量,所以这里才会没有使用 var 声明,直接覆盖了变量 |
@mqyqingfeng 明白了,感谢! |
再问一个问题哈,实际项目中什么场景下会出现循环引用的情况呢?如果没有的话,为什么underscore不选择直接跳出去呢? |
@liujuntao123 我在项目中也没有遇到循环引用的问题……至于为什么不跳出来,或许是因为当判断这样一个循环引用对象的时候 a = { foo: { b: { foo: { c: { foo: null } } } } };
b = { foo: { b: { foo: { c: { foo: null } } } } };
a.foo.b.foo.c.foo = a;
b.foo.b.foo.c.foo = b; 只有当 clone 到最深层次的对象的时候,才能发现这是一个循环引用,既然都 clone 到最后了,直接跳出来不就白 clone 了~ |
假装看懂了,离写出这样高大上的代码还差十万八千里 |
应该是为了让这种情况进deepEq里面去:a=2,b=Number(2); |
感觉最后这个对象和数组的比较,可以借鉴用来做深拷贝了...(又是另一个无底洞问题) |
请问用了stack之后,就不会变成无限循环了吗 |
switch (className) {
case '[object RegExp]':
case '[object String]':
return '' + a === '' + b;
case '[object Number]':
if (+a !== +a) return +b !== +b;
return +a === 0 ? 1 / +a === 1 / b : +a === +b;
case '[object Date]':
case '[object Boolean]':
return +a === +b;
} 作者你好,经测试 |
@TCutter 你没认真看吧 考虑NaN的情况 |
用之前Object键值对去重的灵感 可以写成这样吗
|
`var type = typeof a;
也许你会好奇是不是少了一个 typeof b !== function? 试想如果我们添加上了这句,当 a 是基本类型,而 b 是函数的时候,就会进入 deepEq 函数,而去掉这一句,就会进入直接进入 false,实际上 基本类型和函数肯定是不会相等的,所以这样做代码又少,又可以让一种情况更早退出。` 大神,既然a是基本类型,b为函数可以直接判断为false;那a为函数,b为基本类型的时候是不是也应该可以判断为false呢?按文中这种判断,当a为函数,b为基本类型的时候貌似会进入deepEq函数啊! |
大佬非常厉害啊,但这个方法还有一点小问题:如果属性中的数据有数组,数组中的数据虽然一致,但顺序不同的话得到结果是false,比如: let a, b;
a = {foo: {b: {foo: {c: {foo: [2, 1]}}}}};
b = {foo: {b: {foo: {c: {foo: [1, 2]}}}}};
console.log('最终结果=======》', eq(a, b)); // false 如果我的想法有错误还望指正,谢谢! |
// 过滤掉两个函数的情况 |
根据这个判断两个对象循环引用的思想,写了个深拷贝处理循环引用的版本。
|
|
构造函数里有个name属性,是否可以通过 |
麻烦问下最后的pop是为了处理内存占用的问题吗 |
你写的 equal函数, equal('a', 'a') // false |
看不懂呀 |
请问一下
是不是还可以添加a为object/function,但b为基本类型的情况咧? |
var obj1 = { a: 1 };
var obj2 = obj1;
obj2.b = 2
eq(obj1, obj2) // ==> true 这种情况的话是不是有问题... |
所以如果 aCtor 是函数,并且 aCtor instanceof aCtor 就说明 aCtor 是 Object 函数
|
两个函数不相等,为什么还能进入到deepEq
这部分为甚要判断是否为函数,应该只能是object能进入啊 |
关注你很久了,文章思路很清晰,理解很透彻啊!有一个问题想请教下,下面的代码中为何 前两行输出是
|
补充一下instanceof的模拟实现: function instance_of(leftVaule, rightVaule) {
let rightProto = rightVaule.prototype; // 取右表达式的 prototype 值
leftVaule = leftVaule.__proto__; // 取左表达式的__proto__值
while (true) {
if (leftVaule === null) {
return false;
}
if (leftVaule === rightProto) {
return true;
}
leftVaule = leftVaule.__proto__
}
} |
前言
虽然标题写的是如何判断两个对象相等,但本篇我们不仅仅判断两个对象相等,实际上,我们要做到的是如何判断两个参数相等,而这必然会涉及到多种类型的判断。
相等
什么是相等?在《JavaScript专题之去重》中,我们认为只要
===
的结果为 true,两者就相等,然而今天我们重新定义相等:我们认为:
不仅仅是这些长得一样的,还有
更复杂的我们会在接下来的内容中看到。
目标
我们的目标是写一个 eq 函数用来判断两个参数是否相等,使用效果如下:
在写这个看似很简单的函数之前,我们首先了解在一些简单的情况下是如何判断的?
+0 与 -0
如果 a === b 的结果为 true, 那么 a 和 b 就是相等的吗?一般情况下,当然是这样的,但是有一个特殊的例子,就是 +0 和 -0。
JavaScript “处心积虑”的想抹平两者的差异:
即便如此,两者依然是不同的:
也许你会好奇为什么要有 +0 和 -0 呢?
这是因为 JavaScript 采用了IEEE_754 浮点数表示法(几乎所有现代编程语言所采用),这是一种二进制表示法,按照这个标准,最高位是符号位(0 代表正,1 代表负),剩下的用于表示大小。而对于零这个边界值 ,1000(-0) 和 0000(0)都是表示 0 ,这才有了正负零的区别。
也许你会好奇什么时候会产生 -0 呢?
那么我们又该如何在 === 结果为 true 的时候,区别 0 和 -0 得出正确的结果呢?我们可以这样做:
NaN
在本篇,我们认为 NaN 和 NaN 是相等的,那又该如何判断出 NaN 呢?
利用 NaN 不等于自身的特性,我们可以区别出 NaN,那么这个 eq 函数又该怎么写呢?
eq 函数
现在,我们已经可以去写 eq 函数的第一版了。
也许你会好奇是不是少了一个
typeof b !== function
?试想如果我们添加上了这句,当 a 是基本类型,而 b 是函数的时候,就会进入 deepEq 函数,而去掉这一句,就会进入直接进入 false,实际上 基本类型和函数肯定是不会相等的,所以这样做代码又少,又可以让一种情况更早退出。
String 对象
现在我们开始写 deepEq 函数,一个要处理的重大难题就是 'Curly' 和 new String('Curly') 如何判断成相等?
两者的类型都不一样呐!不信我们看 typeof 的操作结果:
可是我们在《JavaScript专题之类型判断上》中还学习过更多的方法判断类型,比如 Object.prototype.toString:
神奇的是使用 toString 方法两者判断的结果却是一致的,可是就算知道了这一点,还是不知道如何判断字符串和字符串包装对象是相等的呢?
那我们利用隐式类型转换呢?
看来我们已经有了思路:如果 a 和 b 的 Object.prototype.toString的结果一致,并且都是"[object String]",那我们就使用 '' + a === '' + b 进行判断。
可是不止有 String 对象呐,Boolean、Number、RegExp、Date呢?
更多对象
跟 String 同样的思路,利用隐式类型转换。
Boolean
Date
RegExp
Number
嗯哼?你确定 Number 能这么简单的判断?
可是 a 和 b 应该被判断成 true 的呐~
那么我们就改成这样:
deepEq 函数
现在我们可以写一点 deepEq 函数了。
构造函数实例
我们看个例子:
虽然
person
和animal
都是{name: 'Kevin'}
,但是person
和animal
属于不同构造函数的实例,为了做出区分,我们认为是不同的对象。如果两个对象所属的构造函数对象不同,两个对象就一定不相等吗?
并不一定,我们再举个例子:
尽管
attrs
没有原型,{name: "Bob"}
的构造函数是Object
,但是在实际应用中,只要他们有着相同的键值对,我们依然认为是相等。从函数设计的角度来看,我们不应该让他们相等,但是从实践的角度,我们让他们相等,所以相等就是一件如此随意的事情吗?!对啊,我也在想:undersocre,你怎么能如此随意呢!!!
哎,吐槽完了,我们还是要接着写这个相等函数,我们可以先做个判断,对于不同构造函数下的实例直接返回 false。
数组相等
现在终于可以进入我们期待已久的数组和对象的判断,不过其实这个很简单,就是递归遍历一遍……
循环引用
如果觉得这就结束了,简直是太天真,因为最难的部分才终于要开始,这个问题就是循环引用!
举个简单的例子:
再复杂一点的,比如:
为了给大家演示下循环引用,大家可以把下面这段已经精简过的代码复制到浏览器中尝试:
嗯,以上的代码是死循环。
那么,我们又该如何解决这个问题呢?underscore 的思路是 eq 的时候,多传递两个参数为 aStack 和 bStack,用来储存 a 和 b 递归比较过程中的 a 和 b 的值,咋说的这么绕口呢?
我们直接看个精简的例子:
之所以注释掉
aStack.pop()
和bStack.pop()
这两句,是为了方便大家查看 aStack bStack的值。最终的 eq 函数
最终的代码如下:
真让人感叹一句:eq 不愧是 underscore 中实现代码行数最多的函数了!
专题系列
JavaScript专题系列目录地址:https://github.com/mqyqingfeng/Blog。
JavaScript专题系列预计写二十篇左右,主要研究日常开发中一些功能点的实现,比如防抖、节流、去重、类型判断、拷贝、最值、扁平、柯里、递归、乱序、排序等,特点是研(chao)究(xi) underscore 和 jQuery 的实现方式。
如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎 star,对作者也是一种鼓励。
The text was updated successfully, but these errors were encountered: