js的参数传递:一个bug引发的思考

83 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第7天,点击查看活动详情

问题描述

之前在做一个树形功能的时候,遇到一个问题,就是在map循环里面直接把item的值清空,赋值成空对象{},结果代码不起作用,代码如下:

// 代码1
let arr = [{a: 1}];
arr.map((item) => {
    item = {}
});
console.log(arr); // [{a: 1}]

我们看到尽管我们把空对象{}赋值给了item,但是原来的数组是没有变的,但是我们可以改变item里面的属性,例如:

// 代码2
let arr = [{a: 1}];
arr.map((item) => {
    item.a = 2;
});
console.log(arr); // [{a: 2}]

js的参数都是按值传递

为什么会出现上面的问题,我查阅了一些资料,在《JavaScript高级程序设计》里看到了这么一段描述:

ECMAScript中所有函数的参数都是按值传递的。也就是说,把函数外部的值复制给函数内部的参数,就和把值从一个变量复制到另一个变量一样。基本类型值的传递如同基本类型变量的复制样,而引用类型值的传递,则如同引用类型变量的复制一样。有不少开发人员在这一点上可能会感到困惑,因为访问变量有按值和按引用两种方式,而参数只能按值传递。

在向参数传递基本类型的值时,被传递的值会被复制给一个局部变量(即命名参数,或者ECMAScript的概念来说,就是arguments对象中的一个元素)。在向参数传递引用类型的值时,会把这个值在内存中的地址复制给一个局部变量, 因此这个局部变量的变化会反映在函数的外部。

所以我们上述代码1中的item拿到的并不是实际的arr里面的元素,而是复制了一份一模一样的对象({a: 1})出来,item = {}这行代码只能把新对象({a: 1})改掉,原来的arr里面的还是没有变化的。

image.png

那么为什么代码2可以改item 的属性呢,因为由于对象是引用类型,新对象和旧对象指向的都是同一个地址,因为上面的复制相当于浅拷贝,改了对象属性原来的引用地址也会改变。如果是深拷贝的话那就不会。例如下面代码:

let arr = [{a: 1}];
arr.map((item) => {
    let row = JSON.parse(JSON.stringify(item));
    row.a = 22;
});
console.log(arr); // [{a: 1}]

我们深拷贝了itemrow之后,再修改row 的属性,原来的arr是不会被改变的。所以以后我们开发的时候不想改变之前的对象属性,记得进行深拷贝哦。

那么我们需要改变原来arr的元素应该怎么样呢,我们可以直接使用数组下标arr[index] = {} 进行修改,例如:

let arr = [{a: 1}];
arr.map((item, index) => {
    arr[index] = {};
});
console.log(arr); // [{}]

结语:在《JavaScript高级程序设计》一书里面还讲了很好例子来说明,这里我就不加累述了,大家感兴趣的可以查阅一下。