Giter VIP home page Giter VIP logo

Comments (60)

bailinlin avatar bailinlin commented on May 2, 2024 5

啊啊啊,循环引用好变态,看了好几遍~

from blog.

jimStyle88 avatar jimStyle88 commented on May 2, 2024 3

做为一个前端菜鸟,表示看不懂阿

from blog.

garfield02 avatar garfield02 commented on May 2, 2024 3

`var type = typeof a;
if (type !== 'function' && type !== 'object' && typeof b != 'object') return false;

// 更复杂的对象使用 deepEq 函数进行深度比较
return deepEq(a, b);

也许你会好奇是不是少了一个 typeof b !== function?

试想如果我们添加上了这句,当 a 是基本类型,而 b 是函数的时候,就会进入 deepEq 函数,而去掉这一句,就会进入直接进入 false,实际上 基本类型和函数肯定是不会相等的,所以这样做代码又少,又可以让一种情况更早退出。`

大神,既然a是基本类型,b为函数可以直接判断为false;那a为函数,b为基本类型的时候是不是也应该可以判断为false呢?按文中这种判断,当a为函数,b为基本类型的时候貌似会进入deepEq函数啊!

from blog.

shouhe avatar shouhe commented on May 2, 2024 2

循环引用的比较还是有点复杂的

from blog.

Yxliam avatar Yxliam commented on May 2, 2024 1

@shouhe 比如a是 {'a':1} 然后执行{'a':1} !== {'a':1} 不是直接返回 b !== b 了,没有执行下去了啊

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 2, 2024 1

@liujuntao123 这段是用来判断两个不是同一个构造函数的实例对象是不相等的,之所以 aCtor instanceof aCtor 是用来判断 Object 构造函数的,因为:

Object instanceof Object // true

所以如果 aCtor 是函数,并且 aCtor instanceof aCtor 就说明 aCtor 是 Object 函数

from blog.

jawil avatar jawil commented on May 2, 2024 1

判断 Object 是否相等,我觉得不应该用 Object.keys()

var a = { a: 1, b: 2, c: 3 };
var b = { a: 1, b: 2, c: 3, d: 4 };
Object.defineProperty(b, "d", { enumerable: false });
console.log(eq(a, b)); // true
//  明显这两个对象不对,输出的却是true

for...in循环:会遍历对象自身的属性,以及原型属性,for...in 循环只遍历可枚举(不包括 enumerablefalse )属性。像 ArrayObject 使用内置构造函数所创建的对象都会继承自 Object.prototypeString.prototype 的不可枚举属性;

Object.key():可以得到自身可枚举的属性,但得不到原型链上的属性;

Object.getOwnPropertyNames():可以得到自身所有的属性(包括不可枚举),但得不到原型链上的属性, Symbols 属性也得不到.

Reflect.ownKeys:该方法用于返回对象的所有属性,基本等同于 Object.getOwnPropertyNames()Object.getOwnPropertySymbols 之和。

const parent = {
  a: 1,
  b: 2,
  c: 3
};
const child = {
  d: 4,
  e: 5,
  [Symbol()]: 6
};
child.__proto__ = parent;
Object.defineProperty(child, "d", { enumerable: false });

for (var attr in child) {
  console.log("for...in:", attr);// a,b,c,e
}
console.log("Object.keys:", Object.keys(child));// [ 'e' ]
console.log("Object.getOwnPropertyNames:", Object.getOwnPropertyNames(child)); // [ 'd', 'e' ]
console.log("Reflect.ownKeys:", Reflect.ownKeys(child)); //  [ 'd', 'e', Symbol() ]

综合上面的例子,所以我觉得最应该用 for - in循环判断最好,还有就是如果对象是这种情况,那么算不算相等呢?

var a = { a: 1, b: 2, c: 3 };
var b = { a: 1, b: 2, c: 3, };
Object.defineProperty(b, "c", { enumerable: false });

from blog.

baijunpeng666 avatar baijunpeng666 commented on May 2, 2024 1

// 过滤掉两个函数的情况
if (typeof a != 'object' || typeof b != 'object') return false
为什么是一个等号,不是两个等号。。。!==

from blog.

FrontToEnd avatar FrontToEnd commented on May 2, 2024

当初看underscore的eq源码看的真是头大!

from blog.

liuxinqiong avatar liuxinqiong commented on May 2, 2024

真是头大,还好楼主解释清楚,不知道大神们都是如何考虑到这么多的情况的

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 2, 2024

@liuxinqiong 这或许就是开源项目的好处,会不停有人提 issue 帮忙改进~

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 2, 2024

@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;
    }
  };
}

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 2, 2024

@jimStyle88 哪里没有看懂?可以留言讨论呀~

from blog.

jimStyle88 avatar jimStyle88 commented on May 2, 2024

我的技术水平水平问题,不过还是会经常逛逛你的github ,挺详细的。

from blog.

Yxliam avatar Yxliam commented on May 2, 2024

if (a !== a) return b !== b;
请问下这个怎么理解?

from blog.

shouhe avatar shouhe commented on May 2, 2024

@jerrymark1 判断NAN

from blog.

Yxliam avatar Yxliam commented on May 2, 2024

明白了 @shouhe ,谢谢

from blog.

zhangenming avatar zhangenming commented on May 2, 2024

@mqyqingfeng
console.log(eq(1,'1'))
console.log(eq(Infinity,new String('Infinity')))

是否有必要返回true

from blog.

HuangQiii avatar HuangQiii commented on May 2, 2024

其实我大部分情况下,都是用JSON.stringify来判断的

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 2, 2024

@HuangQiii 哈哈,够用就可以啦~

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 2, 2024

@zhangenming 如果问的是"是否有必要"的话,这个其实要看需求是什么样的…… 在现在的实现中,这两个结果都会返回 false,因为两个参数并不是相同的类型,无论是使用 typeof 还是 Object.prototype.toString 的结果,对于 1 和 New Number(1) 而言,虽然 typeof 的结果不同,但是 Object.prototype.toString 的结果确是一样的

from blog.

liujuntao123 avatar liujuntao123 commented on May 2, 2024

image
大神,请问一下这里该怎么理解呢?

from blog.

liujuntao123 avatar liujuntao123 commented on May 2, 2024

image
这里的length是第一次声明,但是前面却没有var,是故意升级为全局变量,还是遗漏了呢?我看了下全部的代码,感觉应该是遗漏了吧?

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 2, 2024

@liujuntao123 感谢指出哈~ 并不是故意升级为全局变量的,在写这一块的时候,确实是第一次使用 length 变量,理应使用 var 声明,不过从最终的源码中看的话,因为之前已经声明了 length 变量,所以这里才会没有使用 var 声明,直接覆盖了变量

default

from blog.

liujuntao123 avatar liujuntao123 commented on May 2, 2024

@mqyqingfeng 明白了,感谢!
image

from blog.

liujuntao123 avatar liujuntao123 commented on May 2, 2024

再问一个问题哈,实际项目中什么场景下会出现循环引用的情况呢?如果没有的话,为什么underscore不选择直接跳出去呢?

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 2, 2024

@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 了~

from blog.

dowinweb avatar dowinweb commented on May 2, 2024

假装看懂了,离写出这样高大上的代码还差十万八千里

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 2, 2024

@dowinweb 哈哈,代码也不是一蹴而就的,都是慢慢改出来的~

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 2, 2024

@jawil 好久不见呀,我一直很好奇你的花名是什么呢……

即使用了 for in:

var a = { a: 1, b: 2, c: 3 };
var b = { a: 1, b: 2, c: 3, d: 4 };
Object.defineProperty(b, "d", { enumerable: false });
console.log(eq(a, b)); 

这个结果还是 true 吧……

而且在 underscore 的实现中,有将构造函数进行比较的过程,所以也就不需要判断原型中的属性了。

var a = { a: 1, b: 2, c: 3 };
var b = { a: 1, b: 2, c: 3, };
Object.defineProperty(b, "c", { enumerable: false });

这种情况是否相等,就看设计者偏好啦,如果是我,我认为是不相等,既然设置成了不可枚举,就表明不希望被用于比较,从实际开发的角度,觉得还是不相等比较好~

from blog.

jawil avatar jawil commented on May 2, 2024

@mqyqingfeng 对象这个我其实觉得比较要考虑的东西比较多,不仅仅是 key 值了,其他都感觉都 ok 了。

来阿里内网一查 jawil 就知道我的花名和真名了,哈哈哈,2333

from blog.

Tan90Qian avatar Tan90Qian commented on May 2, 2024

比较好奇一点:不管是自己实现还是借用第三方库,在拥有了这样一个eq函数之后,前端会在怎样的场景下遇到需要用到它?萌新感觉如果仅仅是业务需求的话,因为涉及的数据都比较明确,基本可以通过一些简单的方式去判断。需要用到eq这样强大方法的复杂的场景似乎并没有遇到过,然而面试中经常会碰到类似这样的问题,比较困惑。 @mqyqingfeng @jawil

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 2, 2024

@Tan90Qian 嘿嘿,我也没有遇到过非要使用 eq 的场景,不知道 @jawil 在阿里这种大业务场景下有木有?😂

from blog.

SWorry avatar SWorry commented on May 2, 2024

image
大神,这块的意义是什么呢,我把这个去掉了测试一下,结果都相同啊

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 2, 2024

@SWorry 如果去掉的话,会有问题呦:

function eq(a, b){
    if (a === b) return 1 / a === 1 / b;
    return false;
}

console.log(eq('a', 'a')); // false

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 2, 2024

@Tan90Qian 我突然发现其实我用 eq 的时候还蛮多的,只是我以前没有意识到……

一个常用的场景的就是单元测试中的断言,比如:

QUnit.test("set.forEach", function(assert) {
	let temp = [];
	let set = new Set([1, 2, 3]);
	set.forEach((value, key) => temp.push(value * 2) )

	assert.deepEqual(temp, [2, 4, 6], "Passed!");
});

deepEqual 其实就是 eq 函数

from blog.

xwcp avatar xwcp commented on May 2, 2024

var type = typeof a;
if (type !== 'function' && type !== 'object' && typeof b != 'object') return false;
这里为什么还要判断typeof b != 'object'啊

from blog.

zhangjing28 avatar zhangjing28 commented on May 2, 2024

image
这里的过滤掉两个函数的情况,是取的关系,那么为什么下面的判断还要加上isFunction的判断呐?
对于这两部分的判断不是很清楚,麻烦帮忙解释下,谢谢

from blog.

lishihong avatar lishihong commented on May 2, 2024

var type = typeof a;
if (type !== 'function' && type !== 'object' && typeof b != 'object') return false;
这里为什么还要判断typeof b != 'object'啊

应该是为了让这种情况进deepEq里面去:a=2,b=Number(2);

from blog.

HXWfromDJTU avatar HXWfromDJTU commented on May 2, 2024

循环引用的比较还是有点复杂的

感觉最后这个对象和数组的比较,可以借鉴用来做深拷贝了...(又是另一个无底洞问题)

from blog.

Annie2694 avatar Annie2694 commented on May 2, 2024

请问用了stack之后,就不会变成无限循环了吗

from blog.

Mcqueengit avatar Mcqueengit commented on May 2, 2024

感谢作者的分享,另外在 构造函数实例处,是不是aCtor和bCtor只要有一个不是Object构造函数就可以满足条件了
QQ截图20190517162256

from blog.

TCutter avatar TCutter commented on May 2, 2024
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;
    }

作者你好,经测试NumberBooleanDate均可以使用'' + a === '' + b;判断,为什么还要做上面那种区分?

from blog.

NameWjp avatar NameWjp commented on May 2, 2024

@TCutter 你没认真看吧 考虑NaN的情况

from blog.

happycaroline avatar happycaroline commented on May 2, 2024

用之前Object键值对去重的灵感 可以写成这样吗

function eq(a,b){
    var toString = Object.prototype.toString
    console.log(toString.call(a) + JSON.stringify(a))
    console.log(toString.call(b) + JSON.stringify(b))
    return toString.call(a) + JSON.stringify(a) === toString.call(b) + JSON.stringify(b)
} 

from blog.

lvao813 avatar lvao813 commented on May 2, 2024

大佬非常厉害啊,但这个方法还有一点小问题:如果属性中的数据有数组,数组中的数据虽然一致,但顺序不同的话得到结果是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

如果我的想法有错误还望指正,谢谢!

from blog.

KeithChou avatar KeithChou commented on May 2, 2024

根据这个判断两个对象循环引用的**,写了个深拷贝处理循环引用的版本。
本质上,就是将递归的值存储到stack中,如果相等,那么直接return回去,避免递归爆栈问题。

function deepCopy (source: object, stack?: any[]): object {
    if (!source) return source
    var root: object = {}
    if (Array.isArray(source)) root = []
    var keys: string[] = Object.keys(source)
    var len: number = keys.length
    var key: any = null

    // 存储递归的值
    // 如果发现循环引用,则直接return,避免递归爆栈
    stack = stack || []
    var stackLen: number = stack.length

    while (stackLen--) {
        if (stack[stackLen] === source) {
            return source
        }
    }
    stack.push(source)
    
    while (len--) {
        key = keys[len]
        if (typeof source[key] === 'object') {
            root[key] = deepCopy(source[key], stack)
        } else {
            root[key] = source[key]
        }
    }

    stack.pop()
    return root
}

from blog.

returnMaize avatar returnMaize commented on May 2, 2024
// 在判断 0 和 -0 的时候
function eq(a, b){
    if (a === b) return a !== 0 || 1 / a === 1 / b;
    return false;
}

// 写成这种形式会不会更好(可读性更强一点,代码量也更少一点)
function eq(a, b) {
  return a === b && a / 1 === b / 1
}

from blog.

CrisChr avatar CrisChr commented on May 2, 2024

构造函数里有个name属性,是否可以通过if(aCons.constructor.name === bCons.constructor.name)来判断两个构造函数实例对象相等?

from blog.

daolou avatar daolou commented on May 2, 2024
function isFunction(obj) {
    return toString.call(obj) === '[object Function]'
}

判断 是不是函数这块儿 有问题, 有可能是 [object GeneratorFunction][object AsyncFunction]

所以我认为用 typeof 更好些

image

from blog.

Hezhongming avatar Hezhongming commented on May 2, 2024

麻烦问下最后的pop是为了处理内存占用的问题吗

from blog.

anjina avatar anjina commented on May 2, 2024
// 在判断 0 和 -0 的时候
function eq(a, b){
    if (a === b) return a !== 0 || 1 / a === 1 / b;
    return false;
}

// 写成这种形式会不会更好(可读性更强一点,代码量也更少一点)
function eq(a, b) {
  return a === b && a / 1 === b / 1
}

你写的 equal函数, equal('a', 'a') // false

from blog.

vipduqian avatar vipduqian commented on May 2, 2024

看不懂呀

from blog.

troubleFisher avatar troubleFisher commented on May 2, 2024
现在,我们已经可以去写 eq 函数的第一版了。

// eq 第一版
// 用来过滤掉简单的类型比较,复杂的对象使用 deepEq 函数进行处理
function eq(a, b) {

    // === 结果为 true 的区别出 +0 和 -0
    if (a === b) return a !== 0 || 1 / a === 1 / b;

    // typeof null 的结果为 object ,这里做判断,是为了让有 null 的情况尽早退出函数
    if (a == null || b == null) return false;

    // 判断 NaN
    if (a !== a) return b !== b;

    // 判断参数 a 类型,如果是基本类型,在这里可以直接返回 false
    var type = typeof a;
    if (type !== 'function' && type !== 'object' && typeof b != 'object') return false;

    // 更复杂的对象使用 deepEq 函数进行深度比较
    return deepEq(a, b);
};
也许你会好奇是不是少了一个 typeof b !== function?

试想如果我们添加上了这句,当 a 是基本类型,而 b 是函数的时候,就会进入 deepEq 函数,而去掉这一句,就会进入直接进入 false,实际上 基本类型和函数肯定是不会相等的,所以这样做代码又少,又可以让一种情况更早退出。

请问一下

  if (type !== 'function' && type !== 'object' && typeof b != 'object') return false;

是不是还可以添加a为object/function,但b为基本类型的情况咧?

from blog.

ZHUBFeng avatar ZHUBFeng commented on May 2, 2024
  var obj1 = { a: 1 };
  var obj2 = obj1;
  obj2.b = 2

  eq(obj1, obj2) // ==> true

这种情况的话是不是有问题...

from blog.

yinju123 avatar yinju123 commented on May 2, 2024

@liujuntao123 这段是用来判断两个不是同一个构造函数的实例对象是不相等的,之所以 aCtor instanceof aCtor 是用来判断 Object 构造函数的,因为:

Object instanceof Object // true

所以如果 aCtor 是函数,并且 aCtor instanceof aCtor 就说明 aCtor 是 Object 函数
好像不一定是 Object 函数

function fn() { }
  fn.prototype.constructor = fn
  fn.__proto__ = fn.prototype
  console.log(111, fn instanceof fn) // true

from blog.

yinju123 avatar yinju123 commented on May 2, 2024

两个函数不相等,为什么还能进入到deepEq

if (type !== 'function' && type !== 'object' && typeof b != 'object') return false;

这部分为甚要判断是否为函数,应该只能是object能进入啊

from blog.

codeshareman avatar codeshareman commented on May 2, 2024

关注你很久了,文章思路很清晰,理解很透彻啊!有一个问题想请教下,下面的代码中为何 前两行输出是 true,还望解答下:

console.log(Function instanceof Function); // true
console.log(Object instanceof Object);   // true
console.log(Array instanceof Array); // false 

from blog.

GeorgeSmith215 avatar GeorgeSmith215 commented on May 2, 2024

image
console.log(Function instanceof Function); // true
这里是因为Function.prototype === Function.__proto__
console.log(Object instanceof Object); // true
这里是因为Object.prototype === Object.__proto__.__proto__
console.log(Array instanceof Array); // false
这里是因为Array.prototype !== Array.__proto__(注:Array.__proto__ === Function.prototype)且Array.prototype !== Array.__proto__.__proto__(注:Array.__proto__.__proto__ === Object.prototype)且Array.prototype !== Array.__proto__.__proto__.__proto__(注:Array.__proto__.__proto__.__proto__ === null)

from blog.

GeorgeSmith215 avatar GeorgeSmith215 commented on May 2, 2024

补充一下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__ 
    }
}

from blog.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.