JavaScript数组相等判断:从引用比较到深比较的完整指南

JavaScript数组(四):判断数组相等的4种方法 如果我们需要比较两个数组是否相等,不能像比较基本类型(String、Number、Boolean等)一样,使用===(或==) 来判断,所以如果要比较数组是否相等,需要使用一些特殊方法。 阅读详情

1. 项目概述:为什么“数组相等”是个技术活?

刚接触JavaScript那会儿,我也觉得判断两个数组是否相等是件再简单不过的事。不就是 arr1 == arr2 或者 arr1 === arr2 吗?结果第一次在项目里这么写,就踩了个大坑。两个看起来一模一样的数组,用 === 比较,返回的永远是 false 。那一刻我才明白,在JavaScript的世界里,数组的“相等”远不止字面意思那么简单。这背后涉及到引用比较、值比较、嵌套结构、特殊值处理等一系列问题,是前端开发中一个高频且容易出错的细节。

无论是表单数据比对、状态管理中的脏检查、单元测试断言,还是实现一个自定义的深比较函数,判断数组相等都是绕不开的基础操作。网上搜一下,你会发现从简单的循环遍历到复杂的递归算法,各种方案五花八门。但哪种方案最可靠?哪种性能最好?在什么场景下该用哪种?这些问题往往没有标准答案,需要根据数组的结构、数据量以及对特殊值(如 NaN , undefined , 稀疏数组,甚至循环引用)的处理要求来综合决定。

这篇文章,我就结合自己多年踩坑和填坑的经验,把JavaScript中判断数组相等的各种方法掰开揉碎了讲清楚。我会从最基础的引用比较讲起,逐步深入到浅比较、深比较,分析每种方法的原理、适用场景和潜在陷阱,并给出可以直接“抄作业”的代码实现和性能优化建议。无论你是正在学习JavaScript的新手,还是想巩固基础的中高级开发者,相信都能从中获得一些实用的启发。

2. 核心概念拆解:从“相等”的定义说起

在深入代码之前,我们必须先统一对“数组相等”这个概念的理解。在不同的上下文和需求下,“相等”的标准可能截然不同。

2.1 引用相等 vs. 值相等

这是最根本的区分,也是新手最容易混淆的地方。

引用相等 指的是两个变量指向内存中的同一个数组对象。在JavaScript中,数组是引用类型, == 或 === 运算符比较的是引用地址,而不是数组的内容。

const arr1 = [1, 2, 3];
const arr2 = arr1; // arr2 是 arr1 的引用
const arr3 = [1, 2, 3]; // arr3 是一个全新的数组对象

console.log(arr1 === arr2); // true,指向同一个对象
console.log(arr1 === arr3); // false,内容相同,但引用不同

注意 :在绝大多数业务场景下,我们关心的都是值相等,即两个独立的数组是否包含相同的元素。引用相等通常只在你想知道“是不是同一个数组对象”时有用,比如在React的 useEffect 依赖数组中,如果你错误地传入了一个新创建但内容相同的数组,可能会导致不必要的重渲染。

值相等 则要求两个数组:

  1. 长度相同。
  2. 在相同索引位置上的元素“相等”。

而元素的“相等”又引出了下一个问题:什么是元素的相等?对于基本类型( number , string , boolean , null , undefined , symbol , bigint ),值相等就是值相同。但对于对象(包括数组、函数、普通对象等),就需要进一步定义是浅比较还是深比较。

2.2 浅比较 vs. 深比较

这是处理嵌套数据结构时的核心概念。

浅比较 只比较数组的第一层元素。如果元素是基本类型,则比较值;如果元素是对象(包括子数组),则比较它们的引用是否相同。

const arrA = [1, { name: 'Alice' }];
const arrB = [1, { name: 'Alice' }];

// 一个简单的浅比较函数(后面会实现)
function shallowEqual(arr1, arr2) {
  if (arr1.length !== arr2.length) return false;
  for (let i = 0; i < arr1.length; i++) {
    if (arr1[i] !== arr2[i]) return false; // 对于对象,这里比较的是引用!
  }
  return true;
}

console.log(shallowEqual(arrA, arrB)); // false! 因为第二个元素是两个不同的对象

深比较 则会递归地比较数组的所有层级,直到所有叶子节点都是基本类型,并确保它们的值相等。

// 一个理想的深比较结果
deepEqual(arrA, arrB); // 应该返回 true,因为递归比较后,对象的内容是相同的。

选择浅比较还是深比较,完全取决于你的数据结构。如果你的数组里只包含基本类型,或者你明确知道并接受“对象引用相同才视为相等”的规则,那么浅比较就足够了,它的性能也远高于深比较。反之,如果你的数组可能嵌套了复杂的对象或数组,并且你需要比较它们的内容,就必须使用深比较。

2.3 特殊值的处理

一个健壮的相等判断函数必须妥善处理JavaScript中的各种“怪咖”值。

  • NaN : NaN 是JavaScript中唯一一个不等于自身的值。 NaN === NaN 返回 false 。因此,在比较时,需要单独判断一个值是否是 NaN (通常使用 Number.isNaN() )。
  • +0 和 -0 : 在常规的 === 比较中, +0 === -0 返回 true 。但在某些严格的数学或规范场景下,它们被视为不同。大多数情况下可以忽略,但需要知晓。
  • null 和 undefined : null === undefined 返回 false 。你需要决定在你的比较逻辑中,是否要将它们视为相等。通常, null 只等于 null , undefined 只等于 undefined 。
  • 稀疏数组 : 例如 const arr = []; arr[5] = 'foo'; ,这个数组的 length 是6,但索引0到4的位置都是“空槽”。遍历时,这些空槽的值是 undefined ,但它们与直接赋值为 undefined 在行为上有细微差别(例如在 Array.prototype.map 中会被跳过)。一个严谨的比较函数可能需要考虑这一点。
  • 循环引用 : 这是深比较中最棘手的问题。当一个对象(或数组)通过属性间接或直接地引用自身时,就形成了循环引用。一个不处理循环引用的深比较函数会陷入无限递归,导致栈溢出。
// 循环引用示例
const obj = {};
obj.self = obj;
const arrX = [obj];
const arrY = [obj];
// 深比较 arrX 和 arrY 时,如果不处理循环引用,会在比较 obj.self 时无限递归下去。

理解了这些核心概念,我们才能有的放矢地选择和实现合适的比较方法。

3. 常用方法实现与深度剖析

接下来,我们逐一实现并分析几种常见的数组相等判断方法,从简单到复杂,从片面到全面。

3.1 方法一:JSON.stringify 的“捷径”与巨坑

这是网络上流传最广的“一行代码解决”方案。

function arraysEqualJSON(arr1, arr2) {
  return JSON.stringify(arr1) === JSON.stringify(arr2);
}

原理 :将两个数组都序列化成JSON字符串,然后比较字符串是否相等。

优点 :

  • 代码极其简洁。
  • 对于只包含可序列化值(字符串、数字、布尔、null、普通对象和数组)且顺序一致的数组,它能实现深比较的效果。

致命缺点与坑点 :

  1. 顺序敏感 : JSON.stringify 对对象的属性顺序不保证稳定(虽然现代引擎通常按定义顺序,但规范未强制)。如果数组元素是对象,且属性顺序不同,即使内容相同,字符串也可能不同。
    console.log(JSON.stringify({a:1,b:2}) === JSON.stringify({b:2,a:1})); // 可能为 false
    
  2. 无法处理特殊值 :
    • undefined 、函数、 Symbol 在序列化时会被忽略(在数组中会变成 null ?不,在数组中直接变成 null ,在对象中则整个键值对消失)。
    console.log(arraysEqualJSON([undefined], [null])); // true! 错误
    console.log(arraysEqualJSON([function(){}], [null])); // true! 错误
    
    • NaN 和 Infinity 会被序列化为 null 。
    console.log(arraysEqualJSON([NaN], [null])); // true! 错误
    
    • 循环引用会导致序列化报错。
  3. 性能问题 :对于大数组或复杂对象,序列化和字符串比较的开销很大。

实操心得 :这个方法 仅适用于 临时、快速的调试,或者你100%确定数组仅包含基础类型、且不需要考虑 undefined 等边界情况。 绝对不要 将其用于生产环境的正式逻辑或测试用例中,它的不可靠性会带来难以排查的Bug。

3.2 方法二:基于循环的浅比较

这是最基础、最可靠的手动实现方法,适用于一维数组或明确只需浅比较的场景。

function arraysShallowEqual(arr1, arr2) {
  // 1. 检查是否为同一个引用(可选,但能快速通过)
  if (arr1 === arr2) return true;

  // 2. 检查长度
  if (arr1.length !== arr2.length) return false;

  // 3. 遍历比较每个元素
  for (let i = 0; i < arr1.length; i++) {
    // 使用 Object.is 进行严格比较,它能正确处理 NaN 和 +0/-0
    if (!Object.is(arr1[i], arr2[i])) {
      return false;
    }
  }

  // 4. 所有元素都通过比较
  return true;
}

关键点解析 :

  • Object.is(value1, value2) :这是比 === 更严格的相等比较。它解决了 === 的两个“缺陷”:
    • Object.is(NaN, NaN) 返回 true 。
    • Object.is(+0, -0) 返回 false 。 如果你希望 NaN 等于自身,并且不区分 +0 和 -0 ,使用 Object.is 是最佳选择。如果不需要,用 === 也可以。
  • 提前引用检查 : if (arr1 === arr2) return true; 这行是一个有效的优化。如果两个变量指向同一个数组,那么它们的内容必然相同,无需再遍历。
  • 稀疏数组 :这个实现能正确处理稀疏数组,因为 for 循环会访问到每个索引(包括空槽,其值为 undefined ),并与另一个数组相同索引的值(也是 undefined )用 Object.is 比较,结果是 true 。

适用场景 :一维数组,且元素均为基本类型,或你明确希望进行引用比较。

局限性 :对于嵌套对象或数组,它比较的是引用。 [{a:1}] 和 [{a:1}] 会被判定为不相等。

3.3 方法三:递归实现深比较

当需要比较嵌套结构时,我们必须实现一个递归函数。这是一个功能相对完整的深比较实现:

function isDeepEqual(value1, value2) {
  // 使用 Object.is 进行基本类型和特殊值(NaN)的比较
  if (Object.is(value1, value2)) {
    return true;
  }

  // 判断是否为对象类型(包括数组和普通对象,排除null)
  if (
    typeof value1 !== 'object' ||
    value1 === null ||
    typeof value2 !== 'object' ||
    value2 === null
  ) {
    return false; // 其中一个是非对象,且Object.is已返回false,则不相等
  }

  // 处理数组
  if (Array.isArray(value1) && Array.isArray(value2)) {
    if (value1.length !== value2.length) return false;
    for (let i = 0; i < value1.length; i++) {
      if (!isDeepEqual(value1[i], value2[i])) return false;
    }
    return true;
  }

  // 处理普通对象
  // 如果一个是数组一个是对象,则不相等
  if (Array.isArray(value1) || Array.isArray(value2)) {
    return false;
  }

  // 获取对象自身的所有键(包括不可枚举和Symbol?这里只使用可枚举属性,通常足够)
  const keys1 = Object.keys(value1);
  const keys2 = Object.keys(value2);
  if (keys1.length !== keys2.length) return false;

  for (const key of keys1) {
    // 检查 key 是否在 value2 中,并递归比较其值
    if (!Object.prototype.hasOwnProperty.call(value2, key)) {
      return false;
    }
    if (!isDeepEqual(value1[key], value2[key])) {
      return false;
    }
  }

  return true;
}

// 用于数组的包装函数
function arraysDeepEqual(arr1, arr2) {
  return isDeepEqual(arr1, arr2);
}

实现细节与考量 :

  1. 递归入口 : isDeepEqual 是一个通用函数,可以比较任意两个值。
  2. 类型检查顺序 :先处理基本类型的快速返回( Object.is ),再处理对象。对于 null , typeof null === 'object' ,所以需要显式排除。
  3. 数组与对象区分 :使用 Array.isArray() 明确区分数组和普通对象。一个数组和一个拥有数字键的普通对象不应该被视为相等。
  4. 属性比较 :对于对象,我们通常只比较自身可枚举的字符串键属性。这符合大多数场景。如果你需要比较 Symbol 键或不可枚举属性,需要调整 Object.keys() 的部分。
  5. 循环引用问题 : 上面的实现没有处理循环引用! 这是它的一个重大缺陷。传入循环引用的数据结构会导致无限递归和栈溢出。

3.4 方法四:处理循环引用的健壮深比较

为了解决循环引用,我们需要一个记忆机制来记录已经比较过的对象对,如果再次遇到,则根据情况判断。

function isDeepEqualCyclic(value1, value2, seen = new WeakMap()) {
  // 基本类型和特殊值比较
  if (Object.is(value1, value2)) {
    return true;
  }

  // 对象类型检查
  if (
    typeof value1 !== 'object' ||
    value1 === null ||
    typeof value2 !== 'object' ||
    value2 === null
  ) {
    return false;
  }

  // --- 循环引用检查 ---
  // 如果这对对象已经在比较中,则认为它们相等(防止无限递归)
  // 使用 WeakMap,键是对象,值是与之比较的另一个对象的 Map
  let seenForValue1 = seen.get(value1);
  if (seenForValue1) {
    // 如果 value1 之前和 value2 比较过,说明遇到了循环引用,且当时认为它们相等(或正在比较中)
    // 这里我们返回 true,表示在本次递归路径上,它们被视为相等,以继续其他部分的比较。
    // 更严谨的做法是存储比较结果,这里简化处理。
    return seenForValue1.get(value2) === true;
  }
  // 记录当前比较对
  if (!seen.has(value1)) {
    seen.set(value1, new WeakMap());
  }
  seen.get(value1).set(value2, true);
  // --- 循环引用检查结束 ---

  // 数组比较
  if (Array.isArray(value1) && Array.isArray(value2)) {
    if (value1.length !== value2.length) return false;
    for (let i = 0; i < value1.length; i++) {
      if (!isDeepEqualCyclic(value1[i], value2[i], seen)) return false;
    }
    // 比较完成,可以清理这条记录(可选,WeakMap 会自动管理)
    // seen.get(value1).delete(value2);
    return true;
  }

  // 混合类型(数组 vs 对象)
  if (Array.isArray(value1) || Array.isArray(value2)) {
    return false;
  }

  // 普通对象比较
  const keys1 = Reflect.ownKeys(value1); // 获取所有键,包括 Symbol
  const keys2 = Reflect.ownKeys(value2);
  if (keys1.length !== keys2.length) return false;

  for (const key of keys1) {
    if (!Reflect.has(value2, key)) {
      return false;
    }
    if (!isDeepEqualCyclic(value1[key], value2[key], seen)) {
      return false;
    }
  }
  return true;
}

核心改进 :

  • WeakMap 的使用 : WeakMap 以对象为键,且是弱引用,不会阻止垃圾回收,适合做这种临时记录。 seen 这个Map的结构是: seen.set(objA, innerMap) ,其中 innerMap.set(objB, true) 表示 objA 正在与 objB 进行比较。
  • Reflect.ownKeys() :替换 Object.keys() ,以同时获取字符串键和 Symbol 键,使比较更加全面。
  • 循环引用处理逻辑 :当函数再次遇到一对已经记录在 seen 中的对象时,它直接返回 true 。这意味着,在当前的递归路径上,我们假设这两个对象是相等的,从而跳出无限递归。这是一种保守且合理的策略,因为如果两个结构是循环引用的,并且在其他所有部分都相等,那么它们整体上应该被视为相等。

这个版本是一个生产环境可用的、健壮的深比较函数的基础框架。你可以根据具体需求调整其严格程度(比如是否比较原型链、是否比较不可枚举属性等)。

4. 性能优化与高级技巧

实现功能只是第一步,让代码在真实场景中高效运行同样重要。

4.1 性能优化策略

  1. 短路优化 :在递归比较中,尽早发现不相等的情况并返回 false ,避免不必要的深度遍历。我们的代码中,长度检查、键数量检查、属性存在性检查都是短路优化。
  2. 类型预判 :在递归前,可以先对常见类型进行快速路径判断。例如,如果两个值都是 Date 对象,可以直接比较 .getTime() ;如果都是 RegExp 对象,可以比较 .source 和 .flags 。
  3. 避免不必要的递归 :对于非常大的对象,递归深度可能导致栈溢出。虽然循环引用处理部分缓解了这个问题,但对于深度嵌套的非循环结构,递归仍有风险。可以考虑使用迭代(栈或队列)的方式实现深比较,但这会大大增加代码复杂度。对于前端绝大多数场景,递归深度是足够的。
  4. 使用 Map 缓存比较结果 :如果在一个生命周期内需要反复比较相同的几组大型对象,可以建立一个缓存( Map ),将 (value1, value2) 的对作为键(需要解决键的生成问题,例如使用 WeakMap 和唯一标识),存储比较结果。但这通常属于过度优化,仅在性能瓶颈明确时考虑。

4.2 利用现代JavaScript API

  • Array.prototype.every() :可以使浅比较的代码更函数式、更简洁,但会失去短路能力( every 在遇到 false 后会停止,所以实际上仍有短路)。
    function arraysShallowEqualEvery(arr1, arr2) {
      return arr1.length === arr2.length && arr1.every((val, idx) => Object.is(val, arr2[idx]));
    }
    
  • 第三方库 :在大型项目中,重新造轮子可能引入未知Bug。成熟的工具库提供了经过千锤百炼的相等判断函数。
    • Lodash : _.isEqual(value, other) 是一个非常强大的深比较函数,处理了几乎所有边界情况,包括循环引用、TypedArrays、Buffer等。性能也经过优化。
    • Underscore : _.isEqual() 。
    • Node.js 内置 assert : assert.deepStrictEqual(actual, expected) ,主要用于测试。

实操心得 :我的建议是,在个人学习或小型工具函数中,可以自己实现以加深理解。但在严肃的商业项目中, 优先考虑使用 Lodash 的 _.isEqual 。它的可靠性、性能和浏览器兼容性都经过了充分验证,能节省大量开发和测试时间,避免潜在的边界情况Bug。

4.3 在框架与生态中的实践

  • React : React 的渲染优化依赖于浅比较。 React.memo 、 PureComponent 、 useMemo 、 useCallback 的依赖项数组比较,都是使用 Object.is 进行的浅比较。这就是为什么当依赖项是对象或数组时,你需要保证引用稳定,否则优化会失效。
  • 状态管理(如 Redux) : Redux 要求 reducer 是纯函数,状态更新需要返回新的对象/数组。在 connect 或 useSelector 中,会通过浅比较来判断状态是否变化,从而决定是否重新渲染组件。理解数组/对象的相等性对编写高效的 Redux 应用至关重要。
  • 测试框架(如 Jest) : Jest 的 toEqual() 匹配器就是一个深比较匹配器。它比 toBe() (引用比较)更常用。了解其原理有助于编写更准确的测试断言。

5. 实战场景与避坑指南

理论说再多,不如看几个真实场景下的应用和容易踩的坑。

5.1 场景一:表单数据脏检查

假设你有一个编辑用户信息的表单,初始数据来自服务器。你想在用户点击保存时,只提交修改过的字段(增量更新)。

const initialData = { name: 'Alice', hobbies: ['reading', 'coding'] };
let currentData = { ...initialData }; // 用户可能修改此对象

// 错误做法:直接比较对象
function hasChangedWrong() {
  return currentData !== initialData; // 总是 true,因为 currentData 是新对象
}

// 错误做法:用 JSON.stringify 比较
function hasChangedJSON() {
  return JSON.stringify(currentData) !== JSON.stringify(initialData);
}
// 如果用户把 hobbies 数组的顺序从 ['reading', 'coding'] 改成 ['coding', 'reading'],内容没变,但字符串变了,会误判为已修改。

// 推荐做法:深比较(或针对性的浅比较)
import { isEqual } from 'lodash-es'; // 使用 Lodash
function hasChanged() {
  return !isEqual(currentData, initialData);
}

// 或者,如果你知道只有第一层或特定字段会变,可以写针对性的比较
function hasChangedSpecific() {
  return currentData.name !== initialData.name ||
         !arraysShallowEqual(currentData.hobbies, initialData.hobbies);
}

避坑点 :脏检查的关键是定义清楚“变化”的含义。是全量深比较,还是比较特定字段?数组顺序变化是否算变化?这些都需要根据业务逻辑来定。盲目使用 JSON.stringify 或简单的引用比较会导致逻辑错误。

5.2 场景二:React 性能优化

在React函数组件中, useEffect 、 useMemo 、 useCallback 的依赖数组使用浅比较。

const [list, setList] = useState([{ id: 1, text: 'item' }]);

// 情况1:依赖项是新建的数组,每次渲染都不同,导致 effect 无限执行
useEffect(() => {
  console.log('List changed');
}, [list]); // `list` 的引用在每次渲染时是稳定的,没问题

const filteredList = list.filter(item => item.id > 0);
useEffect(() => {
  console.log('Filtered list changed (probably wrong)');
}, [filteredList]); // 错误!filter 每次都会返回一个新数组,effect 每次渲染都会执行

// 正确:用 useMemo 缓存结果,保证引用稳定
const memoizedFilteredList = useMemo(() => {
  return list.filter(item => item.id > 0);
}, [list]); // 依赖 list,只有当 list 变化时才会重新计算

useEffect(() => {
  console.log('Filtered list changed (correct)');
}, [memoizedFilteredList]);

// 情况2:依赖项是对象字面量,每次都是新引用
const config = { threshold: 0.5 };
useEffect(() => {
  console.log('Config changed');
}, [config]); // 错误!config 是每次渲染新创建的对象,effect 每次都会执行

// 正确:将 config 移到组件外,或用 useMemo/useState 包裹
const stableConfig = useMemo(() => ({ threshold: 0.5 }), []);
// 或者
const [configState] = useState({ threshold: 0.5 });

避坑点 :牢记React的依赖比较是浅比较。对于对象和数组,确保它们的引用在依赖项不变时是稳定的。善用 useMemo 、 useCallback 、 useState 来管理引用。

5.3 场景三:实现一个自定义的 deepEqual 测试工具

假设你在为一个工具库编写单元测试,但测试框架的断言不够灵活,你需要自己实现一个深比较并输出详细的差异信息。

function deepEqualWithDiff(a, b, path = '') {
  if (Object.is(a, b)) {
    return { isEqual: true };
  }

  // 类型不同
  if (typeof a !== typeof b) {
    return { isEqual: false, path, reason: `类型不同: ${typeof a} vs ${typeof b}` };
  }

  // 处理 null
  if (a === null || b === null) {
    return { isEqual: a === b, path, reason: `null 比较不一致` };
  }

  // 都是对象类型
  if (typeof a === 'object') {
    // 数组比较
    const isArrayA = Array.isArray(a);
    const isArrayB = Array.isArray(b);
    if (isArrayA !== isArrayB) {
      return { isEqual: false, path, reason: `一方是数组,另一方不是` };
    }

    if (isArrayA && isArrayB) {
      if (a.length !== b.length) {
        return { isEqual: false, path, reason: `数组长度不同: ${a.length} vs ${b.length}` };
      }
      for (let i = 0; i < a.length; i++) {
        const result = deepEqualWithDiff(a[i], b[i], `${path}[${i}]`);
        if (!result.isEqual) {
          return result; // 返回第一个不相等处的差异
        }
      }
      return { isEqual: true };
    }

    // 普通对象比较
    const keysA = Object.keys(a);
    const keysB = Object.keys(b);
    const allKeys = new Set([...keysA, ...keysB]);

    for (const key of allKeys) {
      const hasA = keysA.includes(key);
      const hasB = keysB.includes(key);
      if (!hasA || !hasB) {
        return { isEqual: false, path: `${path}.${key}`, reason: `属性缺失,一方有,一方无` };
      }
      const result = deepEqualWithDiff(a[key], b[key], `${path}.${key}`);
      if (!result.isEqual) {
        return result;
      }
    }
    return { isEqual: true };
  }

  // 其他基本类型(数字、字符串等)直接不相等
  return { isEqual: false, path, reason: `值不同: ${a} vs ${b}` };
}

// 使用示例
const obj1 = { arr: [1, { x: 2 }] };
const obj2 = { arr: [1, { x: 3 }] }; // 内部 x 的值不同
const diff = deepEqualWithDiff(obj1, obj2);
console.log(diff);
// 输出类似: { isEqual: false, path: '.arr[1].x', reason: '值不同: 2 vs 3' }

这个增强版的深比较不仅返回布尔值,还能告诉你第一个差异出现在哪个路径上以及原因,对于调试复杂数据结构的不匹配非常有用。

避坑点 :自己实现深比较用于测试时,要特别注意错误信息的清晰度。一个好的差异报告能极大提升调试效率。同时,要处理好循环引用,避免在测试复杂对象时栈溢出。

判断两个JavaScript数组是否相等,这个看似简单的问题,贯穿了引用与值、浅层与深层、基本类型与复杂对象、性能与准确性等多方面的权衡。从简单的 === 到健壮的、处理循环引用的深比较函数,再到利用 _.isEqual 这样的工业级解决方案,选择哪种方法完全取决于你的具体场景。

对于日常开发,我的经验是: 优先使用稳定可靠的第三方库(如Lodash)来处理复杂的相等判断 ,把精力集中在业务逻辑上。但在面试或需要深入理解语言特性时,能手写实现一个考虑周全的深比较函数,无疑是能力的体现。最重要的是,无论采用哪种方式,都要清楚地知道它的比较规则和边界在哪里,这样才能写出正确、健壮的代码。下次当你再遇到需要比较数组或对象时,不妨先停下来想一想:我需要的,到底是哪一种“相等”?

Extjs 性能优化 High Performance ExtJs 本文适合有一定javascript基础(明确js的面向对象,继承,作用域等)并能熟练使用extjs框架(明确各组件间的继承关系)的人阅读,目的在于对extjs的前台架构进行性能上的优化,核心思想为:“按需索取,晚使用,晚加载晚声明;不使用,不加载不声明;经常访问使用,进行缓存”。如果你不知道这里在说什么,下面就是一堆Dog shit,不要再看了。 立即下载

相关推荐

开发extjs程序可查看页面源代码

extjs程序在开发调试时,看不到页面的实际源代码,借助该资源文件可以查看,方便调试。

Ext.js性能优化漫谈

Ext.js是一个用于建立企业级应用的纯JS框架。毫无疑问,它为我们提供了大量的组件,比如container,panel,field,grid,这些组件使用起来很方便,不需要去写js和html,但是ext.js的性能却存在很大的问题。比如,我在公司负责的页面,在本地的加载时间居然需要十几秒,当然这可能和后台服务有关,但是前台的渲染和执行也耗费了大量的时间。下面就我个人感受和网上的一些信息对Ext.j

neal1991的专栏 2101

【无人机智能避障】基于深度强化学习的城市复杂环境路径规划实战

本文探讨了基于深度强化学习的无人机在城市复杂环境中的智能避障与路径规划。针对传统算法难以应对动态与非结构化障碍的痛点,文章详细介绍了如何利用DQN算法,通过设计合理的状态空间、奖励函数,并结合经验回放、目标网络等技巧,训练无人机自主学会在楼宇间安全、高效飞行的策略。

weixin_28224217的博客 727

Extjs性能问题

总觉得我的Extjs程序加载慢,运行起来很卡,部分很快,在客户端浏览器中呈现,效果很好,效率很低 运行缓慢特别在列表中如果数据量大拖拉列表极为缓慢,如果列表上再加入了编辑框,那会更加缓慢 带有数据量大grid的tabpanel切换也有严重卡顿现象。 参看网上各位的经验,总结如下: 1,减少要加载的东西 不要动不动就ext-all.js,extjs是可以定制的,如果你用不到tree,就不要...

weixin_30571465的博客 249

DSP TMS320F280049之CCS工程的建立(库函数版)

TMS320F280049 DSP是TI最新出的一款C2000系列的DSP,主频为100MHZ,功能较为强大,具有较多的片上模拟外设。这一节主要讲述如何新建F280049的CCS工程。 Step1.下载文件: 下载C2000Ware软件并安装。下载地址如下: http://www.ti.com.cn/tool/cn/C2000WARE?keyMatch=C2000Ware%2...

fanxianyan1993的博客 1万+

告别Vysor!用Scrcpy在Mac上无线投屏安卓手机(附魅族16th闪退修复实战)

本文详细介绍了如何在Mac上配置和使用开源投屏工具Scrcpy,实现安卓手机无线投屏的高效解决方案。从基础环境准备到高级功能调优,包括无线连接配置、输入控制增强、多设备管理和性能优化等实用技巧,特别针对魅族16th等机型的闪退问题提供了修复方案,帮助用户彻底告别Vysor等商业软件。

weixin_28712661的博客 389

ExtJS性能太差怎么办?

随着用户体验要求越来越高,很多公司在使用ExtJS作为其前台开发框架, 也深受程序员的喜爱。因为ExtJS有其很多方面的优势,我下面列出几条 比较多的中文学习资料 界面漂亮 强大的控件库 但ExtJS也有其比较大的问题,我列出几条: 超大的js文件、 缓慢的运行速度、 扩展性较差、 学习难度较大(主要体现在代码量大且难以维护) 由于这些问题的存在使很多项目组对ExtJS叹为观

deepwishly专栏 7943

Zemax序列模式下激光二极管(LD)光源建模与像散校正实战

本文详细讲解了在Zemax序列模式下精确建立激光二极管(LD)光源模型并校正其像散的核心方法。通过将椭圆光束的发散角转换为渐晕系数,并巧妙利用理想柱面镜模拟像散,实现了对LD光源特性的高精度模拟。该方法特别适用于激光耦合、激光雷达等需要对像散光束进行精密控制的光学系统设计。

weixin_28455801的博客 430

【译文】ExtJS 4.1性能预览

原文:http://www.sencha.com/blog/ext-js-4-1-developer-preview/ 很高兴,今天发布了一个ExtJS 4.1的性能改善的预览版。在过去几个月,我们一直在努力地改善加载时间、渲染和整个框架内布局的性能,一直使用自己的例子和你们的应用程序做跨浏览器的基准测试和性能优化,并取得了一些显著的改进。 今天,我们将开放当前的生成包,让你的应用可以感受一...

网站开发初中级代码参考-转载 170

金融波动预测:GARCH模型实战解析与ARIMA对比

本文深入解析GARCH模型在金融波动预测中的实战应用,并与传统ARIMA模型进行对比。通过沪深300指数日收益率数据的案例分析,展示GARCH模型如何有效捕捉波动聚集效应,提升极端波动预测准确率。文章包含Python代码实现、参数解读及模型选择指南,为量化交易者提供实用的波动率建模解决方案。

weixin_29098367的博客 188

QGIS插件实战:用百度地图AOI数据做个城市公园分析,从下载到出图全记录

本文详细介绍了如何利用QGIS的百度地图AOI插件进行城市公园分析,从插件安装、数据获取到空间分析与可视化全流程。通过实战案例演示了公园面积统计、服务范围缓冲区分析及专题地图制作,帮助用户高效完成城市绿地评估工作。

weixin_30790841的博客 552

JavaScript 中的相等性判断研究

在 JavaScript 中,理解相等性判断的细微差别对于编写正确和高效的代码至关重要。本文将深入探讨 JavaScript 中的三种相等性判断方式,详细阐述其中的难点和易错点。

qq_33546823的博客 1818

考完CAIE认证一年后,谈谈它对我的职业的真实影响(干货)

大门,让面试官愿意给你机会,但进门之后的发展,还得靠持续的学习和项目积累。说实话,当时的心态是“考都考了,总比闲着强”,对这张证书能带来什么实际价值,心里并没有底。这种广泛的生态认可,让这张证书的“流通性”变得很强——无论跳槽还是在原公司发展,它都是一个被市场验证过的能力证明。对我个人而言,虽然没跳槽,但在年度调薪和争取内部项目时,持证经历也成了一个有力的“加分项”。这种巨大的薪资差距,恰恰反映了市场对“能落地、懂技术”的深度人才的渴求。考完证后的这一年,我看到了两条清晰的职业发展路径在自己面前展开。

qq_16014487的博客 808

JS 深比较相等

JS 深比较相等   首先来看几个例子: console.log(0 === 0); // true console.log('str' === 'str'); // true console.log(true === true); // true console.log(undefined === undefined); // true console.log(null === null); // true console.log({} === {}); // false console.log

_ 872

运放实现阶梯波发生器-总结报告

实验目的: 学习、理解、掌握方波电路、限幅电路积分累加电路、反馈电路 课程条件: 电脑仿真软件Multisim 原理框图和实验思路: 方波电路通过微分电路可以形成正负脉冲,在通过限幅电路后变为单脉冲,最后在积分电路中形成阶梯波。微分电路存在的意义就是为了防止方波频率过大,从而导致在经过限幅电路的方波,在一个周期内将电容的电充满,这样就不会产生阶梯波。如果方波的频率比较小,可以近似认为是正负脉冲,这样就可以跳过微分电路。 ...............

0 1万+

Outlook Express 6 (OE6) for Win7/Win8/Win10

英文版 Outlook Express6.0, 兼容Win7/Win10/WinXP,解压就可用。

js|比较两个【嵌套对象/数组】是否相等(递归法+json法)

文章目录划重点!不能用==/===来判断对象相等方法一:通过JSON.stringfy(obj)来判断两个对象转后的字符串是否相等方法二:通过遍历+迭代深度比较 划重点!不能用==/===来判断对象相等 在js 判断相等常见的就是 == (等同)和===(恒等); ==, 两边值类型不同的时候,要先进行类型转换,再比较 ; ===,不做类型转换,类型不同的一定不等。 js中的对象是引用类型,...

敲敲的博客 7398

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

js 如何判断两个对象相等

什么是相等,一般我们认为只要===的结果是true,两者就相等。 我们一般认为 1、NaN和NaN是相等的 2、[1]和[1]就是相等的 3、{value:1}和{value:1}就是相等的 4、1和new Number(1)是相等的 5、“123”和new String(“123”)是相等的 6、true 和new Boolean(true)是相等的。 其实不然 我们需要写个函数eq来判断两个参...

Ellie 2万+

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

js判断两个对象是否相等的办法,包含绝对相等和形状内容相等

在js中对象是引用类型,对象要相等除非是同一个引用,不然就不会相等,如下: var obj1={0:'a',1:'b',2:'c'} var obj2={0:'a',1:'b',2:'c'} console.log(obj1==obj2) console.log(obj1===obj2) 打印都为false,虽然他们模样一样,当需要判断对象的形状和内容都一样的时候,就比如上面的obj1、obj2,怎么办呢?它来了 完整代码: //判断两个对象是否相同(包含绝对相等和他们是否

编程界明世隐的博客 9390

JavaScript进行数组相等判断

在使用 includes、indexOf 等方法时,要注意这些方法对于非基本类型(如对象、数组)的比较是基于引用的,而不是基于内容的。

2301_80052985的博客 1215

js比较两个数组是否相等

今天在看廖雪峰大神的博客意外地发现javascript是不能用 '=='或'==='操作符直接比较两个数组是否相等的。 先看案例: var a = [1,2,3,4,5]; var b = a.slice(); console.log(a); // [1,2,3,4,5] console.log(b); // [1,2,3,4,5] console.l...

qq_36742720的博客 1478

JavaScript中 如何判断两个对象是否相等?

JavaScript中 如何判断两个对象是否相等? object类型作为JavaScript中的基本数据类型,也是实际前端开发中经常使用的一种数据类型。 对于数字(Number)、字符串(String)、Boolean类型等数据,判断其是否相等,使用 === 全等于符合即可判断。但是对于object类型的数据,进行判断时就需要多做一些处理。 常见的判断方法有: 方法一:   使用 JSON.stringify()方法,将对象转化为字符串。然后使用 “===” 判断即可: let obj1 = { a

lookingForw_4585的博客 1万+

(译) js中的值相等和引用相等

此为翻译文章 在编程语言世界中通常有两种类型的相等: reference equality value equality #引入 思考下面的代码: var x = 12, y = 12; var object = { x: 1, y: 2 }; var object2 = { x: 1, y: 2 }; 复制代码正如你看到的,object和object2有相同的值。如果你看...

weixin_33725126的博客 731

js如何判断两个数组是否相等

今天在实现一个需求的时候,需要去判断两个数组是否相等,看似简单的问题,实则还挺复杂。 首先我们知道数组是引用数据类型,在==或者===比较时,比较的是其地址值,所以[1,2] == [1,2] [1,2] === [1,2] 二者输出的都是false, 那么如何比较两个数组是否相等呢 1. toString()或者join(): 它们的原理都是将二者转换成字符串,针对某一些数组可以进行判断,如:[1,2,3].toString() === [1,2,3].toString() 和 [1...

qq_42132814的博客 9169

JavaScript 中比较对象的四种方式

JavaScript 中比较对象的四种方式 比较 JavaScript 中的原始值非常简单。只需使用任何一种可用的相等运算符即可,例如严格相等运算符: 'a' === 'c'; // => false 1 === 1; // => true 但是对象却有结构化数据,所以比较起来比较困难。在本文中,你将学习如何正确比较 JavaScript 中的对象。 1. 引用比较 JavaScript 提供了 3 种对值进行比较的方法: 严格相等运算符 === 宽松相等运算符 == Object

hkw20的博客 9976

js比较两个单独的数组或对象是否相等

所谓js的中的传值,其实也就是说5种基本数据类型(null,undefind,boolean,number,string) 传引用也就是说的那个引用数据类型,(array和object) 基本数据类型的值不可变,而引用数据类型的值是可变的 所以当你比较数组和对象时,都是false;除非你是克隆的原份数据 即:var a= { name:"李四" };var b= a; 大家通常...

hsany330的专栏 1409

如何判断2个数组相等

2个数组怎么样才算相等,应该是长度相等,每个位置上的元素相等。 那么开始编写这个函数 function ArrayIsEqual(arr1,arr2){//判断2个数组是否相等 if(arr1===arr2){//如果2个数组对应的指针相同,那么肯定相等,同时也对比一下类型 return true; }else{ if(arr1.length!...

前端小胖鼠 3万+
上一篇: Excel数据透视表:从零到精通的动态数据分析与决策指南
下一篇: Windows 11家庭版25H2部署SQL Server 2008 R2与U8V15实战指南
weixin_30247307
博客等级 码龄11年 110粉丝 1407原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值