写了 const b = a; 然后改 b[0],回头发现 a 也变了,排查半小时才想起来数组赋值传的是引用。这个坑在 JS、Java、Python 里都有,只是写法不太一样。

赋值只是多了一个指针

变量里存的是数组在内存中的地址,赋值把地址复制一份,两个变量指向同一块数据。改元素、push、splice,两边都能看到。想互不影响,得复制出一份新数组,光换个变量名没用。判断两个数组是否相等也不能用 ==,引用不同永远返回 false,得逐项比较。

浅拷贝够用的情况

一维数组、元素都是基本类型时,[...arr]、arr.slice()、Array.from(arr) 都能复制。Java 里用 Arrays.copyOf(arr, arr.length),Python 用 arr[:] 或者 list(arr)。改新数组的元素,原数组不受影响。Object.assign([], arr) 也能复制,但同样是浅拷贝,和展开运算符一个效果。slice 对字符串也有效,返回的是新字符串,别和数组方法搞混。

二维数组必须深拷贝

元素还是对象或者数组时,浅拷贝只复制了外层,里层还是同一个引用。改 b[0][1],a 照样跟着变。简单场景用 arr.map(row => [...row]) 逐层复制,结构复杂就上 structuredClone,或者序列化再解析,后者会丢掉函数和 undefined,用之前想清楚。JSON 序列化那个老办法遇到 Date 会把时间变成字符串,特殊类型要单独处理。

函数传参也是这个道理

把数组传进函数,函数里改了元素,外面的数组同样会变。这是很多人调试时找不到原因的地方。不需要修改原数据,就在函数开头复制一份;需要修改,就明确写在注释里,别让调用方猜。框架里也一样,Vue 和 React 改数组要用能触发更新的方法,直接改下标有时页面不动。

记住一句

:赋值不动数据,拷贝才动。数组赋值前先问自己要不要保留原数组,要就复制,不要就直接用,别等出了 bug 再回头找。复制后给新数组起个能看懂的名字,别名太多后面更乱。