当前位置: 首页 > news >正文

JavaScript 中十种数组拷贝方法(从浅拷贝到深拷贝)

在JavaScript开发中,数组拷贝是一项基本但极其重要的操作。无论是数据处理、状态管理还是性能优化,理解不同的数组拷贝方法及其适用场景都至关重要。本文将全面介绍JavaScript中十种数组拷贝方法,包括浅拷贝和深拷贝技术,并通过丰富的代码示例和图表帮助你彻底掌握这些知识点。

一、浅拷贝方法

浅拷贝只复制数组的第一层元素,如果数组包含对象或嵌套数组,这些嵌套元素仍然是共享的。以下是七种常见的浅拷贝方法:

1. 扩展运算符(Spread Operator)

// 原始数组
const originalArray = [1, 2, {name: '张三'}];// 使用扩展运算符进行浅拷贝
const copiedArray = [...originalArray];// 修改拷贝数组的基本类型元素
copiedArray[0] = 100;
console.log(originalArray[0]); // 输出: 1 (未受影响)// 修改拷贝数组中的对象元素
copiedArray[2].name = '李四';
console.log(originalArray[2].name); // 输出: '李四' (原始数组也被修改)

适用场景:React状态更新、Redux reducer中不可变数据更新等。

2. for循环拷贝

const original = ['a', 'b', {title: '标题'}];
const copy = [];// 使用for循环进行浅拷贝
for (let i = 0; i < original.length; i++) {copy[i] = original[i];
}// 验证拷贝效果
copy[1] = 'B';
console.log(original[1]); // 'b' (未改变)copy[2].title = '新标题';
console.log(original[2].title); // '新标题' (被改变)

特点:最基础的拷贝方式,兼容性好但代码略显冗长。

3. while循环拷贝

const source = [10, 20, [30, 40]];
const target = [];
let i = 0;// 使用while循环进行浅拷贝
while (i < source.length) {target[i] = source[i];i++;
}// 测试嵌套数组修改
target[2][0] = 300;
console.log(source[2][0]); // 300 (原始数组被修改)

4. Array.map()方法

const data = [1, 2, {id: 101}];// 使用map进行浅拷贝 - 恒等函数
const dataCopy = data.map(item => item);// 或者使用更简洁的写法
const dataCopy2 = data.map(x => x);// 修改对象属性
dataCopy[2].id = 202;
console.log(data[2].id); // 202 (原始数据被修改)

原理map方法通过对每个元素应用恒等函数(x => x)来创建新数组。

5. Array.filter()方法

const nums = [5, 10, {value: 15}];// 使用filter进行浅拷贝 - 条件始终为true
const numsCopy = nums.filter(() => true);// 测试效果
numsCopy[0] = 50;
console.log(nums[0]); // 5 (未改变)numsCopy[2].value = 150;
console.log(nums[2].value); // 150 (被改变)

6. Array.reduce()方法

const items = ['apple', 'banana', {fruit: 'cherry'}];// 使用reduce进行浅拷贝
const itemsCopy = items.reduce((acc, current) => {acc.push(current);return acc;
}, []);// 验证拷贝效果
itemsCopy[1] = 'BANANA';
console.log(items[1]); // 'banana' (未改变)itemsCopy[2].fruit = 'CHERRY';
console.log(items[2].fruit); // 'CHERRY' (被改变)

7. Array.slice()方法

const colors = ['red', 'green', {code: 'blue'}];// 使用slice进行浅拷贝
const colorsCopy = colors.slice();// 或者指定从开始到结束
const colorsCopy2 = colors.slice(0, colors.length);// 测试修改效果
colorsCopy[0] = 'RED';
console.log(colors[0]); // 'red' (未改变)colorsCopy[2].code = 'BLUE';
console.log(colors[2].code); // 'BLUE' (被改变)

特点slice不传参数时默认返回整个数组的浅拷贝。

8. Array.concat()方法

const langs = ['JavaScript', 'Python', {name: 'Java'}];// 使用concat进行浅拷贝 - 不传参数或传空数组
const langsCopy = langs.concat();
// 或者
const langsCopy2 = langs.concat([]);// 验证拷贝效果
langsCopy[1] = 'PYTHON';
console.log(langs[1]); // 'Python' (未改变)langsCopy[2].name = 'JAVA';
console.log(langs[2].name); // 'JAVA' (被改变)

9. Array.from()方法

const sets = [new Set([1]), new Set([2]), {type: 'set'}];// 使用Array.from进行浅拷贝
const setsCopy = Array.from(sets);// 测试修改效果
setsCopy[0] = new Set([10]);
console.log(sets[0]); // Set {1} (未改变)setsCopy[2].type = 'SET';
console.log(sets[2].type); // 'SET' (被改变)

二、深拷贝方法

深拷贝会复制数组的所有层级,包括嵌套的对象和数组,使得原始数组和拷贝数组完全独立。

1. JSON.parse & JSON.stringify

const complexArray = [{id: 1, name: '项目1'}, [2, 4, 6], new Date(),function test() { console.log('test'); }
];// 使用JSON方法进行深拷贝
const deepCopy = JSON.parse(JSON.stringify(complexArray));// 修改拷贝后的数组
deepCopy[0].id = 100;
deepCopy[1][0] = 200;console.log(complexArray[0].id); // 1 (未改变)
console.log(complexArray[1][0]); // 2 (未改变)// 注意:函数和特殊对象会被忽略
console.log(deepCopy[2]); // 日期被转换为字符串
console.log(deepCopy[3]); // undefined (函数丢失)

局限性

  • 无法拷贝函数、undefined
  • 日期对象会被转换为字符串
  • RegExp、Error等特殊对象会被忽略
  • 会丢失构造函数信息

2. 深拷贝工具函数

function deepClone(obj, hash = new WeakMap()) {// 处理基本类型和nullif (obj === null || typeof obj !== 'object') {return obj;}// 处理日期对象if (obj instanceof Date) {return new Date(obj);}// 处理正则表达式if (obj instanceof RegExp) {return new RegExp(obj);}// 处理循环引用if (hash.has(obj)) {return hash.get(obj);}// 创建新对象/数组const cloneObj = new obj.constructor();hash.set(obj, cloneObj);// 递归拷贝所有属性for (const key in obj) {if (obj.hasOwnProperty(key)) {cloneObj[key] = deepClone(obj[key], hash);}}return cloneObj;
}// 使用示例
const original = {arr: [1, 2, {name: '测试'}],date: new Date(),regex: /test/g
};
const cloned = deepClone(original);

深拷贝方法对比

方法速度功能完整性循环引用支持特殊对象支持
JSON方法不完整不支持有限支持
递归深拷贝完整支持完整支持
lodash.cloneDeep中等完整支持完整支持

三、应用场景与最佳实践

1. 何时使用浅拷贝

  • 原始数组只包含基本数据类型
  • 你确实需要共享嵌套对象的引用
  • 性能要求极高,且数据量较大

2. 何时使用深拷贝

  • 数组包含嵌套对象或数组
  • 需要完全独立的数据副本
  • 避免意外的副作用

3. 实际开发建议

需要拷贝数组
是否包含嵌套对象/数组?
使用深拷贝
使用浅拷贝
是否需要处理特殊对象?
使用自定义深拷贝或lodash
考虑JSON方法
性能要求极高?
使用扩展运算符或slice
选择可读性好的方法如map

四、常见问题解答

Q1:为什么我的数组拷贝后修改还是影响了原数组?

A:这通常是因为你使用了浅拷贝方法,而数组中包含对象或嵌套数组。解决方法是使用深拷贝技术。

Q2:JSON.parse(JSON.stringify())有什么缺点?

A:这种方法会丢失函数、undefined值,将日期转为字符串,无法处理循环引用,且会破坏特殊对象的原型链。

Q3:在React中推荐使用哪种拷贝方法?

A:推荐使用扩展运算符或slice进行浅拷贝,因为React推崇不可变数据流,而浅拷贝已经足够满足大多数状态更新的需求。

五、扩展知识

1. 不可变数据的重要性

在React、Redux等现代前端框架中,不可变数据是核心概念之一。正确的数组拷贝方法可以帮助我们:

  • 更容易追踪数据变化
  • 实现时间旅行调试
  • 优化组件渲染性能

2. 结构化克隆算法

现代浏览器提供了structuredClone API,它是比JSON方法更强大的深拷贝方案:

const original = {set: new Set([1, 2, 3]),map: new Map([['a', 1]]),date: new Date()
};// 使用structuredClone进行深拷贝
const cloned = structuredClone(original);

支持

  • 循环引用
  • Map/Set等复杂对象
  • ArrayBuffer等二进制数据

单词、短语表

单词/短语音标词性词根/词缀释义搭配例子
shallow copy/ˈʃæloʊ ˈkɒpi/名词短语shallow(浅的)+copy(拷贝)浅拷贝perform a ~JavaScript中大多数数组方法是浅拷贝
deep copy/diːp ˈkɒpi/名词短语deep(深的)+copy(拷贝)深拷贝create a ~JSON方法可以实现简单的深拷贝
spread operator/spred ˈɒpəreɪtə/名词短语spread(展开)+operator(操作符)扩展运算符use the ~扩展运算符是ES6引入的便捷语法
immutable/ɪˈmjuːtəbl/形容词im-(不)+mut(变化)+able(能)不可变的~ dataReact推崇不可变数据流
nested/ˈnestɪd/形容词nest(巢)+ed嵌套的~ array处理嵌套数组需要深拷贝
recursion/rɪˈkɜːrʒn/名词re-(再)+curs(跑)+ion递归~ function深拷贝通常需要递归实现
reference/ˈrefrəns/名词re-(回)+fer(带来)+ence引用by ~对象在JavaScript中通过引用传递
circular reference/ˈsɜːrkjələr ˈrefrəns/名词短语circular(循环的)+reference(引用)循环引用handle ~深拷贝需要处理循环引用问题
enumeration/ɪˌnjuːməˈreɪʃn/名词e-(出)+numer(数)+ation枚举property ~JSON.stringify只能序列化可枚举属性
paradigm/ˈpærədaɪm/名词para-(旁)+digm(显示)范式programming ~函数式编程范式强调不可变性

文章转载自:

http://FEQqZIKD.hysqx.cn
http://Yb0Q0fj0.hysqx.cn
http://LUJtHTry.hysqx.cn
http://jVQPOPZk.hysqx.cn
http://xQaXiCRF.hysqx.cn
http://7NiPiXn6.hysqx.cn
http://tgQIX3Sg.hysqx.cn
http://9QNzdN6Z.hysqx.cn
http://Qz47OSaE.hysqx.cn
http://bOs7T5tf.hysqx.cn
http://US5Y1Juq.hysqx.cn
http://uSik034V.hysqx.cn
http://TqCjgVSS.hysqx.cn
http://TTHIuHnb.hysqx.cn
http://gTIVg7pZ.hysqx.cn
http://4WEeae1L.hysqx.cn
http://K0nrIIdV.hysqx.cn
http://PdiZSY1C.hysqx.cn
http://uAo4Hnpw.hysqx.cn
http://5jriRGyv.hysqx.cn
http://V9uBKvfl.hysqx.cn
http://xDALBy2n.hysqx.cn
http://I5WWmJDK.hysqx.cn
http://4LYxhZ2h.hysqx.cn
http://zKbmwUbo.hysqx.cn
http://oHlMGIxs.hysqx.cn
http://1bqaxlCk.hysqx.cn
http://E2nsYCMq.hysqx.cn
http://fBFlpmJC.hysqx.cn
http://VebaHmqz.hysqx.cn
http://www.dtcms.com/a/371812.html

相关文章:

  • 04.事件中心模块
  • 【直接套模板】如何用 Web of Science 精准检索文献?
  • MCP与A2A
  • 数据库索引设计:在 MongoDB 中创建高效索引的策略
  • Shell 秘典(卷十)—— 服务器资源自动化监控脚本的设计与实现
  • 能源电力方向 的创业idea1
  • tf_keras包
  • PyTorch Lightning(训练评估框架)
  • Python进程,线程
  • java设计模式二、工厂
  • Claude Code核心功能操作指南
  • Python Mysql
  • Ansible 角色使用指南
  • 【c++】从三个类的设计看软件架构的哲学思考
  • 695章:使用Scrapy框架构建分布式爬虫
  • X448 算法签名验签流程深度解析及代码示例
  • 基于Apache Flink Stateful Functions的事件驱动微服务架构设计与实践指南
  • 算法题(201):传球游戏
  • 【JavaEE】(23) 综合练习--博客系统
  • 第五十四天(SQL注入数据类型参数格式JSONXML编码加密符号闭合复盘报告)
  • Kotlin 协程之 突破 Flow 限制:Channel 与 Flow 的结合之道
  • RabbitMQ 确认机制
  • DrissionPage 优化天猫店铺商品爬虫:现代化网页抓取技术详解
  • 腾讯云服务器 监控系统 如何查看服务器的并发数量?
  • Qt---对话框QDialog
  • 5G NR-NTN协议学习系列:NR-NTN介绍(1)
  • 9.7需求
  • 43. 字符串相乘
  • 【论文阅读】解耦大脑与计算机视觉模型趋同的因素
  • 20250907 线性DP总结