JavaScript数组push对象是异步操作吗?值重复是何原因
问题解答
首先明确:Array.push() 是完全同步的操作,你遇到的结果异常和异步没有任何关联,本质是JavaScript引用类型的存储特性导致的bug。
根因说明
JavaScript中,对象属于引用类型,变量存储的是对象在堆内存中的地址,而不是对象本身的完整值。
原代码在循环外只声明了一个共用的obj对象,整个循环执行过程中操作的始终是同一块内存地址上的同一个对象:
- 第一次遍历到值为2的元素时,匹配到之前存过的补数8,修改这个公共obj的属性为
{first:8, second:2},再把obj的引用push进结果数组——此时数组里存的不是obj的快照拷贝,只是指向这个公共obj的内存地址 - 循环继续执行,遍历到值为3的元素时,匹配到之前存过的补数7,再次修改同一个公共obj的属性为
{first:7, second:3},再把同一个内存地址push进结果数组 - 循环结束后,结果数组里的两个元素本质是两个指向同一个公共obj的指针,最后一次修改obj属性的操作会覆盖之前的赋值,所以最终打印出来两个元素都是
{first:7, second:3}
原问题逻辑的核心问题代码如下:
// 循环外仅创建1个对象,所有操作都基于这一个对象 let obj = { first: 0, second: 0 } for(let i=0; i<arr.length; i++){ let comp = target - arr[i]; if(mappingArr.has(comp)){ // 每次都是修改同一个对象的属性,没有生成新对象 obj.first = arr[mappingArr.get(comp)]; obj.second = arr[i]; // push的是同一个对象的引用 res.push(obj) } else { mappingArr.set(arr[i], i) } }
原代码预期输出:
[{first: 8, second: 2},{first: 7, second: 3}]
原代码实际输出:[{first: 7, second: 3},{first: 7, second: 3}]
修改后代码正常的原因
调整为在push时直接传入对象字面量的写法后,每次进入匹配判断分支,都会创建一个全新的独立对象,每个新对象都占用单独的内存空间,push进数组的是不同对象的独立引用,互相之间没有关联,后续的操作不会修改之前已经存入数组的对象内容,因此运行结果符合预期。
修改后的核心逻辑:
if(mappingArr.has(comp)){ // 每次命中匹配都生成全新的独立对象,不存在引用复用问题 res.push({first: arr[mappingArr.get(comp)], second: arr[i]}); } else { mappingArr.set(arr[i], i) }
拓展说明
这个问题不是JavaScript独有,Java、C#、Python等所有区分值类型和引用类型的编程语言,都会出现同类问题:只要向集合中存入引用类型的值,默认存入的都是内存地址,而非值的完整拷贝。如果后续修改原引用指向的内存内容,集合中读取到的值也会同步变化。
如果一定要保留提前声明obj的写法,只需要在每次push前为obj重新赋值一个全新对象,切断和之前对象的引用关联即可,示例写法:
if(mappingArr.has(comp)){ // 每次赋值为新对象,覆盖原有引用 obj = { first: arr[mappingArr.get(comp)], second: arr[i] } res.push(obj) }
内容的提问来源于stack exchange,提问作者Krutarth Dave
相关产品推荐
相关产品推荐

