JavaScript:浅拷贝数组两种修改行为的差异原因及原数组防护方法
嘿,这个问题其实戳中了JavaScript里浅拷贝和引用类型的核心点,我给你掰扯明白~
首先得搞懂:数组的浅拷贝(比如用[...arr]、slice()或者Object.assign()),只会拷贝数组本身的第一层结构——但数组里的对象是引用类型,浅拷贝拷贝的只是指向内存中对象的“指针”,不是对象本身。
场景1:修改浅拷贝元素会影响原数组
先看你的代码逻辑:
var peopleOriginal = [{name: 'alex', age:999 }]; var peopleCopy = [...peopleOriginal]; // 浅拷贝数组 peopleCopy[0].age = 100; // 修改拷贝数组里的对象属性 console.log(peopleOriginal[0].age); // 输出100,原数组被改了
原因很简单:peopleCopy[0]和peopleOriginal[0]指向的是内存里同一个对象。你修改的是这个对象的属性,本质是在改内存里的那个“实体”,两个数组的元素都指着它,自然都会看到变化。
场景2:给浅拷贝赋值空数组不影响原数组
再看这个场景:
var peopleOriginal = [{name: 'alex', age:999 }]; var peopleCopy = [...peopleOriginal]; peopleCopy = []; // 直接给拷贝变量赋值新数组 console.log(peopleOriginal); // 还是[{name: 'alex', age:999 }]
这里你不是在修改原来的拷贝数组,而是给peopleCopy这个变量重新分配了一个全新的数组引用。原来的浅拷贝数组和原数组的关联直接断了——peopleOriginal还指着原来的数组,peopleCopy现在指着空数组,两者完全没关系了,原数组当然不受影响。
你忽略的逻辑就是:浅拷贝只处理数组的第一层,嵌套的引用类型(对象、数组)拷贝的是引用;而直接给变量赋值新数组,是改变变量的引用指向,不是修改原引用的内容。
要解决这个问题,你需要做深拷贝——不仅拷贝数组本身,还要把数组里的每个对象也完整复制一份,让它们指向不同的内存地址。给你几种常用方案:
方法1:手动遍历拷贝(适合简单结构)
如果数组里只有一层对象,直接遍历数组,逐个拷贝每个对象就行:
var peopleOriginal = [{name: 'alex', age:999 }]; var peopleCopy = peopleOriginal.map(person => { // 用对象展开语法或者Object.assign拷贝单个对象 return {...person}; // 也可以写 return Object.assign({}, person); }); peopleCopy[0].age = 100; console.log(peopleOriginal[0].age); // 输出999,原数组没变化
方法2:JSON序列化/反序列化(适合纯数据对象)
这种方法简单粗暴,但有局限性——如果对象里有函数、Symbol、undefined或者循环引用,会丢失数据或报错,只适合纯数据结构:
var peopleOriginal = [{name: 'alex', age:999 }]; var peopleCopy = JSON.parse(JSON.stringify(peopleOriginal)); peopleCopy[0].age = 100; console.log(peopleOriginal[0].age); // 输出999
方法3:用第三方库(适合复杂场景)
如果你的项目已经引入了Lodash,直接用cloneDeep最省心,能处理各种嵌套结构和特殊类型:
var peopleOriginal = [{name: 'alex', age:999 }]; var peopleCopy = _.cloneDeep(peopleOriginal); peopleCopy[0].age = 100; console.log(peopleOriginal[0].age); // 输出999
内容的提问来源于stack exchange,提问作者alexr101

