前几天同事老张写了个统计库存的函数,结果调用完之后,外面的原始数据全乱了。他一脸懵:我只是传个参数进去算个数,怎么连原数组都变了?
这问题其实挺常见——函数里的参数,莫名其妙把外部变量给改了。看起来像魔法,其实是编程里一个容易踩的坑。
你以为传的是“值”,其实传的是“地址”
比如在 JavaScript 里,你把一个数组传进函数:
function addNewItem(list) {
list.push('new item');
}
const myItems = ['apple', 'banana'];
addNewItem(myItems);
console.log(myItems); // 输出:['apple', 'banana', 'new item']
你看,函数里只是操作了参数 list,但外面的 myItems 也被改了。因为数组是引用类型,传进去的不是拷贝,而是它在内存里的“地址”。函数拿到这个地址后,直接去原位置修改,自然影响外部。
对象也一样,一改就联动
再看个例子:
function updateUserInfo(user) {
user.logged = true;
}
const person = { name: '小王', logged: false };
updateUserInfo(person);
console.log(person.logged); // true
函数没 return,也没对外声明变量,但 person 的状态还是变了。这种“副作用”在调试时特别隐蔽,尤其函数嵌套多的时候,根本想不到是哪个环节动了数据。
那怎么避免这种“误伤”?
办法是别直接操作入参。想改,先复制一份:
function addNewItemSafe(list) {
const newList = [...list]; // 浅拷贝
newList.push('new item');
return newList;
}
const myItems = ['apple', 'banana'];
const updated = addNewItemSafe(myItems);
console.log(myItems); // 还是 ['apple', 'banana']
console.log(updated); // ['apple', 'banana', 'new item']
这样原数据稳如老狗,函数也干净利落。如果是对象,可以用 {...obj} 或 Object.assign 拷贝。
当然,深层嵌套的对象得用深拷贝,不然内层还是引用。可以自己递归写一个,或者用 JSON.parse(JSON.stringify(obj)) 快速处理(注意不能有函数或 undefined)。
基本类型就安全了吗?
数字、字符串、布尔这些基本类型传进函数,确实是传值,函数里改不影响外面:
function changeNum(x) {
x = 100;
}
let num = 10;
changeNum(num);
console.log(num); // 还是 10
这点和引用类型完全不同。所以判断一个函数会不会“污染”外部,先看它参数是不是对象、数组、函数这类引用类型。
下次遇到数据莫名其妙变了,先去查查有没有哪个函数接收了对象或数组,然后直接调用了 push、splice、sort 这些会修改原数组的方法。这些方法本身就是“原地修改”的,传进去了就危险。
写函数时养成习惯:不打算改外部,就别动参数。要改,就返回新值,让调用方自己决定要不要更新。这样代码更清楚,也不容易埋雷。