JS异步函数中为何修改变量前,打印的await结果已包含修改值
问题本质原因
整个现象由两个JavaScript的基础机制叠加导致,不存在代码执行顺序颠倒的问题:
- 非原始值的引用传递特性
代码里的对象a、嵌套的records数组都属于非原始值,在函数传参、Promise返回值传递的过程中,不会生成独立的完整数据副本,传递的永远是指向内存数据的引用地址。asyncfunction的形参prop、await得到的res1,和你最开始定义的a,本质上都是同一块内存数据的引用句柄,对任意一个句柄的修改,都会直接作用到所有句柄指向的原数据上。 - 控制台的引用值打印延迟读取机制
很多开发者误以为console.log(obj)执行的瞬间,会把obj当时的完整内容做快照后再输出。实际上针对对象、数组这类引用类型,控制台只会先保存这个对象的引用地址,等主线程空闲、渲染控制台日志的时候,才会顺着这个地址读取最新的内存值展示出来。
对应代码的实际执行时序
- 组件初始化,定义对象
a,此时a.data.records的初始值为[1,2,3] useEffect触发执行fetch,调用asyncfunction(a):这里传入的不是a的完整拷贝,只是a的引用地址- 500ms定时器到期,执行
resolve(prop),把同一个引用地址作为Promise的结果返回 await等待完成,将这个引用地址赋值给res1,此时res1.data.records和原a.data.records是同一个数组- 执行
console.log(res1):控制台收到数组的引用,暂存地址但没有立刻读取数组内容 - 紧接着执行同步代码
a.data.records.push(11111):因为操作的是同一块内存地址,这次push直接修改了共享的数组内容 - 主线程空闲开始渲染控制台内容,顺着之前存的数组引用读取最新值,自然就看到了push之后新增的
11111
快速验证方式
如果想让控制台打印出push之前的初始值,只需要在打印时主动生成独立的值快照,打断和原数据的引用关联即可:
- 把打印语句改成
console.log([...res1.data.records]) - 此时控制台拿到的是数组展开后生成的独立副本,保存的是打印时刻的
[1,2,3]快照,后续对原数组的修改不会影响最终的打印结果。
内容的提问来源于stack exchange,提问作者ryan
相关产品推荐
相关产品推荐

