JavaScript 如何做深拷贝

你是不是遇到过这种情况:复制了一个对象,改了新对象的属性,老对象居然也跟着变?比如你用 JavaScript 做一个待办清单,把当前任务列表复制了一份给编辑页面,改编辑页的任务名称,结果列表页的任务也被改掉了——这就是浅拷贝的锅,而深拷贝就是解决这个问题的办法。

很多人刚接触 JavaScript 的拷贝概念,会把“复制”想成复印纸:一份印两份,改其中一份不影响另一份。但实际上,JavaScript 里的基本类型(比如数字、字符串)和引用类型(比如对象、数组)的存储方式完全不一样,拷贝的逻辑也天差地别。简单说,浅拷贝只复制了对象的“地址标签”,两个标签还指向同一个东西,改一个另一个就变;而深拷贝会把对象本身完完整整再做一份,改新的完全不影响旧的。

JavaScript 如何做深拷贝

要理解深拷贝,得先搞懂浅拷贝的问题在哪。举个生活化的例子:你把一个装着苹果的盒子(引用类型)交给朋友,朋友拿到的只是“盒子的钥匙”(地址标签),他开了盒子吃苹果,你自己的盒子里的苹果也没了——这就是浅拷贝的效果。比如代码里用=直接复制对象:

`javascript

const oldObj = { fruit: '苹果', num: 1 };

const newObj = oldObj;

newObj.fruit = '香蕉';

console.log(oldObj.fruit); // 输出'香蕉',老对象被改了

`

要是老对象里的属性都是基本类型,浅拷贝好像还行,可只要有嵌套的对象,问题就立刻出现。

那怎么实现真正的深拷贝?最常用的方法是用JSON.parse和JSON.stringify组合,这个方法简单到不用写复杂逻辑,就像用手机自带的复印功能:先把对象转成字符串(相当于把内容拆成文字),再把字符串转成新对象(相当于用文字印出新盒子)。比如:

`javascript

const oldObj = { fruit: '苹果', details: { color: '红色' } };

const newObj = JSON.parse(JSON.stringify(oldObj));

newObj.details.color = '黄色';

console.log(oldObj.details.color); // 还是'红色',老对象没被改

`

这个方法虽然好用,但有个小缺点:如果对象里有undefined、函数、正则表达式这些特殊值,拷贝后会消失或者变样。比如对象里存了一个function sayHi() {},用这个方法拷贝后,新对象里就没有这个函数了。

如果要处理这种特殊情况,就得用“手动递归”的方法——相当于给每个角落都重新整理一遍。递归就是“自己调用自己”,比如要拷贝一个对象,先检查这个值是不是对象,如果是,就把它的属性挨个再拷贝一遍,直到所有属性都是基本类型为止。代码大概是这样的:

`javascript

function deepClone(target) {

// 如果是基本类型或者null,直接返回

if (typeof target !== 'object' || target === null) return target;

// 区分数组和对象,创建新的空容器

const cloneTarget = Array.isArray(target) ? [] : {};

// 循环拷贝每个属性

for (let key in target) {

// 只拷贝对象自身的属性,避免拷贝原型上的东西

if (target.hasOwnProperty(key)) {

cloneTarget[key] = deepClone(target[key]);

}

}

return cloneTarget;

}

`

这个方法能处理大部分特殊值,除了像Symbol作为键这种情况,不过日常开发里用的概率很低。

最后要提一下:不是所有场景都需要深拷贝。如果你的对象只有一层属性,用Object.assign或者扩展运算符...做浅拷贝就够了,就像只印一张单页的纸,没必要整本复印。只有当对象有嵌套的引用类型属性,且你需要保证新旧对象完全独立时,才需要用深拷贝。

上一篇 PHP 执行超时怎么解决