日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 前端技术 > javascript >内容正文

javascript

JavaScript 专题之如何判断两个对象相等

發(fā)布時(shí)間:2025/3/8 javascript 40 豆豆
生活随笔 收集整理的這篇文章主要介紹了 JavaScript 专题之如何判断两个对象相等 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

JavaScript 專(zhuān)題系列第十二篇,講解如何判斷兩個(gè)參數(shù)是否相等

前言

雖然標(biāo)題寫(xiě)的是如何判斷兩個(gè)對(duì)象相等,但本篇我們不僅僅判斷兩個(gè)對(duì)象相等,實(shí)際上,我們要做到的是如何判斷兩個(gè)參數(shù)相等,而這必然會(huì)涉及到多種類(lèi)型的判斷。

相等

什么是相等?在《JavaScript專(zhuān)題之去重》中,我們認(rèn)為只要 === 的結(jié)果為 true,兩者就相等,然而今天我們重新定義相等:

我們認(rèn)為:

  • NaN 和 NaN 是相等
  • [1] 和 [1] 是相等
  • {value: 1} 和 {value: 1} 是相等
  • 不僅僅是這些長(zhǎng)得一樣的,還有

  • 1 和 new Number(1) 是相等
  • 'Curly' 和 new String('Curly') 是相等
  • true 和 new Boolean(true) 是相等
  • 更復(fù)雜的我們會(huì)在接下來(lái)的內(nèi)容中看到。

    目標(biāo)

    我們的目標(biāo)是寫(xiě)一個(gè) eq 函數(shù)用來(lái)判斷兩個(gè)參數(shù)是否相等,使用效果如下:

    function eq(a, b) { ... }var a = [1]; var b = [1]; console.log(eq(a, b)) // true復(fù)制代碼

    在寫(xiě)這個(gè)看似很簡(jiǎn)單的函數(shù)之前,我們首先了解在一些簡(jiǎn)單的情況下是如何判斷的?

    +0 與 -0

    如果 a === b 的結(jié)果為 true, 那么 a 和 b 就是相等的嗎?一般情況下,當(dāng)然是這樣的,但是有一個(gè)特殊的例子,就是 +0 和 -0。

    JavaScript “處心積慮”的想抹平兩者的差異:

    // 表現(xiàn)1 console.log(+0 === -0); // true// 表現(xiàn)2 (-0).toString() // '0' (+0).toString() // '0'// 表現(xiàn)3 -0 < +0 // false +0 < -0 // false復(fù)制代碼

    即便如此,兩者依然是不同的:

    1 / +0 // Infinity 1 / -0 // -Infinity1 / +0 === 1 / -0 // false復(fù)制代碼

    也許你會(huì)好奇為什么要有 +0 和 -0 呢?

    這是因?yàn)?JavaScript 采用了IEEE_754 浮點(diǎn)數(shù)表示法(幾乎所有現(xiàn)代編程語(yǔ)言所采用),這是一種二進(jìn)制表示法,按照這個(gè)標(biāo)準(zhǔn),最高位是符號(hào)位(0 代表正,1 代表負(fù)),剩下的用于表示大小。而對(duì)于零這個(gè)邊界值 ,1000(-0) 和 0000(0)都是表示 0 ,這才有了正負(fù)零的區(qū)別。

    也許你會(huì)好奇什么時(shí)候會(huì)產(chǎn)生 -0 呢?

    Math.round(-0.1) // -0復(fù)制代碼

    那么我們又該如何在 === 結(jié)果為 true 的時(shí)候,區(qū)別 0 和 -0 得出正確的結(jié)果呢?我們可以這樣做:

    function eq(a, b){if (a === b) return a !== 0 || 1 / a === 1 / b;return false; }console.log(eq(0, 0)) // true console.log(eq(0, -0)) // false復(fù)制代碼

    NaN

    在本篇,我們認(rèn)為 NaN 和 NaN 是相等的,那又該如何判斷出 NaN 呢?

    console.log(NaN === NaN); // false復(fù)制代碼

    利用 NaN 不等于自身的特性,我們可以區(qū)別出 NaN,那么這個(gè) eq 函數(shù)又該怎么寫(xiě)呢?

    function eq(a, b) {if (a !== a) return b !== b; }console.log(eq(NaN, NaN)); // true復(fù)制代碼

    eq 函數(shù)

    現(xiàn)在,我們已經(jīng)可以去寫(xiě) eq 函數(shù)的第一版了。

    // eq 第一版 // 用來(lái)過(guò)濾掉簡(jiǎn)單的類(lèi)型比較,復(fù)雜的對(duì)象使用 deepEq 函數(shù)進(jìn)行處理 function eq(a, b) {// === 結(jié)果為 true 的區(qū)別出 +0 和 -0if (a === b) return a !== 0 || 1 / a === 1 / b;// typeof null 的結(jié)果為 object ,這里做判斷,是為了讓有 null 的情況盡早退出函數(shù)if (a == null || b == null) return false;// 判斷 NaNif (a !== a) return b !== b;// 判斷參數(shù) a 類(lèi)型,如果是基本類(lèi)型,在這里可以直接返回 falsevar type = typeof a;if (type !== 'function' && type !== 'object' && typeof b != 'object') return false;// 更復(fù)雜的對(duì)象使用 deepEq 函數(shù)進(jìn)行深度比較return deepEq(a, b); };復(fù)制代碼

    也許你會(huì)好奇是不是少了一個(gè) typeof b !== function?

    試想如果我們添加上了這句,當(dāng) a 是基本類(lèi)型,而 b 是函數(shù)的時(shí)候,就會(huì)進(jìn)入 deepEq 函數(shù),而去掉這一句,就會(huì)進(jìn)入直接進(jìn)入 false,實(shí)際上 基本類(lèi)型和函數(shù)肯定是不會(huì)相等的,所以這樣做代碼又少,又可以讓一種情況更早退出。

    String 對(duì)象

    現(xiàn)在我們開(kāi)始寫(xiě) deepEq 函數(shù),一個(gè)要處理的重大難題就是 'Curly' 和 new String('Curly') 如何判斷成相等?

    兩者的類(lèi)型都不一樣吶!不信我們看 typeof 的操作結(jié)果:

    console.log(typeof 'Curly'); // string console.log(typeof new String('Curly')); // object復(fù)制代碼

    可是我們?cè)凇禞avaScript專(zhuān)題之類(lèi)型判斷上》中還學(xué)習(xí)過(guò)更多的方法判斷類(lèi)型,比如 Object.prototype.toString:

    var toString = Object.prototype.toString; toString.call('Curly'); // "[object String]" toString.call(new String('Curly')); // "[object String]"復(fù)制代碼

    神奇的是使用 toString 方法兩者判斷的結(jié)果卻是一致的,可是就算知道了這一點(diǎn),還是不知道如何判斷字符串和字符串包裝對(duì)象是相等的呢?

    那我們利用隱式類(lèi)型轉(zhuǎn)換呢?

    console.log('Curly' + '' === new String('Curly') + ''); // true復(fù)制代碼

    看來(lái)我們已經(jīng)有了思路:如果 a 和 b 的 Object.prototype.toString的結(jié)果一致,并且都是"[object String]",那我們就使用 '' + a === '' + b 進(jìn)行判斷。

    可是不止有 String 對(duì)象吶,Boolean、Number、RegExp、Date呢?

    更多對(duì)象

    跟 String 同樣的思路,利用隱式類(lèi)型轉(zhuǎn)換。

    Boolean

    var a = true; var b = new Boolean(true);console.log(+a === +b) // true復(fù)制代碼

    Date

    var a = new Date(2009, 9, 25); var b = new Date(2009, 9, 25);console.log(+a === +b) // true復(fù)制代碼

    RegExp

    var a = /a/i; var b = new RegExp(/a/i);console.log('' + a === '' + b) // true復(fù)制代碼

    Number

    var a = 1; var b = new Number(1);console.log(+a === +b) // true復(fù)制代碼

    嗯哼?你確定 Number 能這么簡(jiǎn)單的判斷?

    var a = Number(NaN); var b = Number(NaN);console.log(+a === +b); // false復(fù)制代碼

    可是 a 和 b 應(yīng)該被判斷成 true 的吶~

    那么我們就改成這樣:

    var a = Number(NaN); var b = Number(NaN);function eq() {// 判斷 Number(NaN) Object(NaN) 等情況if (+a !== +a) return +b !== +b;// 其他判斷 ... }console.log(eq(a, b)); // true復(fù)制代碼

    deepEq 函數(shù)

    現(xiàn)在我們可以寫(xiě)一點(diǎn) deepEq 函數(shù)了。

    var toString = Object.prototype.toString;function deepEq(a, b) {var className = toString.call(a);if (className !== toString.call(b)) return false;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;}// 其他判斷 }復(fù)制代碼

    構(gòu)造函數(shù)實(shí)例

    我們看個(gè)例子:

    function Person() {this.name = name; }function Animal() {this.name = name }var person = new Person('Kevin'); var animal = new Animal('Kevin');eq(person, animal) // ???復(fù)制代碼

    雖然 person 和 animal 都是 {name: 'Kevin'},但是 person 和 animal 屬于不同構(gòu)造函數(shù)的實(shí)例,為了做出區(qū)分,我們認(rèn)為是不同的對(duì)象。

    如果兩個(gè)對(duì)象所屬的構(gòu)造函數(shù)對(duì)象不同,兩個(gè)對(duì)象就一定不相等嗎?

    并不一定,我們?cè)倥e個(gè)例子:

    var attrs = Object.create(null); attrs.name = "Bob"; eq(attrs, {name: "Bob"}); // ???復(fù)制代碼

    盡管 attrs 沒(méi)有原型,{name: "Bob"} 的構(gòu)造函數(shù)是 Object,但是在實(shí)際應(yīng)用中,只要他們有著相同的鍵值對(duì),我們依然認(rèn)為是相等。

    從函數(shù)設(shè)計(jì)的角度來(lái)看,我們不應(yīng)該讓他們相等,但是從實(shí)踐的角度,我們讓他們相等,所以相等就是一件如此隨意的事情嗎?!對(duì)啊,我也在想:undersocre,你怎么能如此隨意呢!!!

    哎,吐槽完了,我們還是要接著寫(xiě)這個(gè)相等函數(shù),我們可以先做個(gè)判斷,對(duì)于不同構(gòu)造函數(shù)下的實(shí)例直接返回 false。

    function isFunction(obj) {return toString.call(obj) === '[object Function]' }function deepEq(a, b) {// 接著上面的內(nèi)容var areArrays = className === '[object Array]';// 不是數(shù)組if (!areArrays) {// 過(guò)濾掉兩個(gè)函數(shù)的情況if (typeof a != 'object' || typeof b != 'object') return false;var aCtor = a.constructor, bCtor = b.constructor;// aCtor 和 bCtor 必須都存在并且都不是 Object 構(gòu)造函數(shù)的情況下,aCtor 不等于 bCtor, 那這兩個(gè)對(duì)象就真的不相等啦if (aCtor == bCtor && !(isFunction(aCtor) && aCtor instanceof aCtor && isFunction(bCtor) && bCtor instanceof bCtor) && ('constructor' in a && 'constructor' in b)) {return false;}}// 下面還有好多判斷 }復(fù)制代碼

    數(shù)組相等

    現(xiàn)在終于可以進(jìn)入我們期待已久的數(shù)組和對(duì)象的判斷,不過(guò)其實(shí)這個(gè)很簡(jiǎn)單,就是遞歸遍歷一遍……

    function deepEq(a, b) {// 再接著上面的內(nèi)容if (areArrays) {length = a.length;if (length !== b.length) return false;while (length--) {if (!eq(a[length], b[length])) return false;}} else {var keys = Object.keys(a), key;length = keys.length;if (Object.keys(b).length !== length) return false;while (length--) {key = keys[length];if (!(b.hasOwnProperty(key) && eq(a[key], b[key]))) return false;}}return true;}復(fù)制代碼

    循環(huán)引用

    如果覺(jué)得這就結(jié)束了,簡(jiǎn)直是太天真,因?yàn)樽铍y的部分才終于要開(kāi)始,這個(gè)問(wèn)題就是循環(huán)引用!

    舉個(gè)簡(jiǎn)單的例子:

    a = {abc: null}; b = {abc: null}; a.abc = a; b.abc = b;eq(a, b)復(fù)制代碼

    再?gòu)?fù)雜一點(diǎn)的,比如:

    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;eq(a, b)復(fù)制代碼

    為了給大家演示下循環(huán)引用,大家可以把下面這段已經(jīng)精簡(jiǎn)過(guò)的代碼復(fù)制到瀏覽器中嘗試:

    // demo var a, b;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;function eq(a, b, aStack, bStack) {if (typeof a == 'number') {return a === b;}return deepEq(a, b) }function deepEq(a, b) {var keys = Object.keys(a);var length = keys.length;var key;while (length--) {key = keys[length]// 這是為了讓你看到代碼其實(shí)一直在執(zhí)行console.log(a[key], b[key])if (!eq(a[key], b[key])) return false;}return true;}eq(a, b)復(fù)制代碼

    嗯,以上的代碼是死循環(huán)。

    那么,我們又該如何解決這個(gè)問(wèn)題呢?underscore 的思路是 eq 的時(shí)候,多傳遞兩個(gè)參數(shù)為 aStack 和 bStack,用來(lái)儲(chǔ)存 a 和 b 遞歸比較過(guò)程中的 a 和 b 的值,咋說(shuō)的這么繞口呢?
    我們直接看個(gè)精簡(jiǎn)的例子:

    var a, b;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;function eq(a, b, aStack, bStack) {if (typeof a == 'number') {return a === b;}return deepEq(a, b, aStack, bStack) }function deepEq(a, b, aStack, bStack) {aStack = aStack || [];bStack = bStack || [];var length = aStack.length;while (length--) {if (aStack[length] === a) {return bStack[length] === b;}}aStack.push(a);bStack.push(b);var keys = Object.keys(a);var length = keys.length;var key;while (length--) {key = keys[length]console.log(a[key], b[key], aStack, bStack)if (!eq(a[key], b[key], aStack, bStack)) return false;}// aStack.pop();// bStack.pop();return true;}console.log(eq(a, b))復(fù)制代碼

    之所以注釋掉 aStack.pop()和bStack.pop()這兩句,是為了方便大家查看 aStack bStack的值。

    最終的 eq 函數(shù)

    最終的代碼如下:

    var toString = Object.prototype.toString;function isFunction(obj) {return toString.call(obj) === '[object Function]' }function eq(a, b, aStack, bStack) {// === 結(jié)果為 true 的區(qū)別出 +0 和 -0if (a === b) return a !== 0 || 1 / a === 1 / b;// typeof null 的結(jié)果為 object ,這里做判斷,是為了讓有 null 的情況盡早退出函數(shù)if (a == null || b == null) return false;// 判斷 NaNif (a !== a) return b !== b;// 判斷參數(shù) a 類(lèi)型,如果是基本類(lèi)型,在這里可以直接返回 falsevar type = typeof a;if (type !== 'function' && type !== 'object' && typeof b != 'object') return false;// 更復(fù)雜的對(duì)象使用 deepEq 函數(shù)進(jìn)行深度比較return deepEq(a, b, aStack, bStack); };function deepEq(a, b, aStack, bStack) {// a 和 b 的內(nèi)部屬性 [[class]] 相同時(shí) 返回 truevar className = toString.call(a);if (className !== toString.call(b)) return false;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;}var areArrays = className === '[object Array]';// 不是數(shù)組if (!areArrays) {// 過(guò)濾掉兩個(gè)函數(shù)的情況if (typeof a != 'object' || typeof b != 'object') return false;var aCtor = a.constructor,bCtor = b.constructor;// aCtor 和 bCtor 必須都存在并且都不是 Object 構(gòu)造函數(shù)的情況下,aCtor 不等于 bCtor, 那這兩個(gè)對(duì)象就真的不相等啦if (aCtor == bCtor && !(isFunction(aCtor) && aCtor instanceof aCtor && isFunction(bCtor) && bCtor instanceof bCtor) && ('constructor' in a && 'constructor' in b)) {return false;}}aStack = aStack || [];bStack = bStack || [];var length = aStack.length;// 檢查是否有循環(huán)引用的部分while (length--) {if (aStack[length] === a) {return bStack[length] === b;}}aStack.push(a);bStack.push(b);// 數(shù)組判斷if (areArrays) {length = a.length;if (length !== b.length) return false;while (length--) {if (!eq(a[length], b[length], aStack, bStack)) return false;}}// 對(duì)象判斷else {var keys = Object.keys(a),key;length = keys.length;if (Object.keys(b).length !== length) return false;while (length--) {key = keys[length];if (!(b.hasOwnProperty(key) && eq(a[key], b[key], aStack, bStack))) return false;}}aStack.pop();bStack.pop();return true;}console.log(eq(0, 0)) // true console.log(eq(0, -0)) // falseconsole.log(eq(NaN, NaN)); // true console.log(eq(Number(NaN), Number(NaN))); // trueconsole.log(eq('Curly', new String('Curly'))); // trueconsole.log(eq([1], [1])); // true console.log(eq({ value: 1 }, { value: 1 })); // truevar a, b;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;console.log(eq(a, b)) // true復(fù)制代碼

    真讓人感嘆一句:eq 不愧是 underscore 中實(shí)現(xiàn)代碼行數(shù)最多的函數(shù)了!

    專(zhuān)題系列

    JavaScript專(zhuān)題系列目錄地址:github.com/mqyqingfeng…。

    JavaScript專(zhuān)題系列預(yù)計(jì)寫(xiě)二十篇左右,主要研究日常開(kāi)發(fā)中一些功能點(diǎn)的實(shí)現(xiàn),比如防抖、節(jié)流、去重、類(lèi)型判斷、拷貝、最值、扁平、柯里、遞歸、亂序、排序等,特點(diǎn)是研(chao)究(xi) underscore 和 jQuery 的實(shí)現(xiàn)方式。

    如果有錯(cuò)誤或者不嚴(yán)謹(jǐn)?shù)牡胤?#xff0c;請(qǐng)務(wù)必給予指正,十分感謝。如果喜歡或者有所啟發(fā),歡迎 star,對(duì)作者也是一種鼓勵(lì)。

    總結(jié)

    以上是生活随笔為你收集整理的JavaScript 专题之如何判断两个对象相等的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。