你可以永远相信debugger,但是不能永远相信console.log
总结放前面:
console.log在打印引用数据类型的时候表现和我们的预期不相符合是因为console.log打印的是引用数据类型的一个快照,因为浏览器或者我们异步代码的原因在快照之后修改了对应的内存空间的值,所以等我们展开打印浏览器通过指针重新访问内存空间的时候会获得最新的值导致展开和不展开的表现不一致。
不知道各位大佬有没有遇到过这样的情况,我在代码里面console.log()了一个数组,然后打开浏览器控制台,看着是空的就像这样[],结果我点展开它里面又有值了,但是在代码打印的位置使用length或者获取数组里面的值都是不行的,🤯 就像下面这样:
let arr = [];
const setFun = () => {
return new Promise((reslove) => {
let arr1 = [1, 2, 3];
setTimeout(() => {
reslove(arr1);
}, 2000)
});
}
const getFun = async () => {
let result = await setFun();
result.forEach((item) => {
arr.push(item);
})
}
getFun();
console.log(arr);
或者说我在某处代码console.log()一个对象,明明控制台打印对象的某一个key是1,但是我展开这个对象里面的key居然是2,我在代码里面获取的也是2,就像下面这样:
不知道各位大佬遇到这样的情况是怎么个想法,反正我第一次遇到的时候我还以为是我的谷歌浏览器出问题了,擦💦我甚至都想卸载重装一波。后来动了动🧠,觉得可能是代码执行顺序的原因,所以我就在代码里面打了断点看了一下,在执行console.log()的时候arr的确是一个空的对象,对arr数组的操作是在console.log()执行之后才进行的。
所以说这到底是为什么呐?
其实这个还是和js的引用数据类型还有console.log()的设计有关系。我们都知道引用数据类型大体上可以说是由两部分组成:指针和内容,指针保存的内容就是一个内存地址的指向,指针一般都是基本数据类型保存在栈内存,内容就包含着这个引用数据类型的实际值一般保存在堆内存。😍 而console.log呐打印的时候只是打印了这个引用数据类型的一个快照,快照中的指针和内容都是照相的时候的内容,在console.log()之后,修改了这个引用数据类型,或者说在这之前修改的操作在一个异步的内容里面,当我们去看打印的时候,这个引用数据类型的内容可能就被修改了,但是因为快照的原因我们看到的还是以前的值。
然后当我们展开的时候,浏览器会利用指针去内存重新读取内容,因为快找的指针是没有发生变化的,所以就看到了改变之后内存,这就是为什么我们展开和不展开看到的结果是不一样的原因了。当然造成这样的原因不一定都是因为我们代码在异步里面操作这个引用数据类型。
还有就是浏览器在进行I/O的时候异步会提升性能,所有这就是为什么有时候我们写的同步代码依然会出现不一致的情况,就像我第二个图一样。
下面就验证一下我上面的想法,当我把上面的代码修改一下,直接替换:
let arr = [];
const setFun = () => {
return new Promise((reslove) => {
let arr1 = [1, 2, 3];
setTimeout(() => {
reslove(arr1);
}, 2000)
});
}
const getFun = async () => {
let result = await setFun();
arr = result; // 修改部分
}
getFun();
console.log(arr);
那么我们看到的结果就和上面不一样了,这个展开的表现是和不展开是一样的。
相信各位大佬也知道是啥原因了,因为这次直接替换,修改的是指针的指向并没有修改之前引用数据饿类型的内存空间,所以当我们展开的时候快照中指针保存的地址还是空的,这样我们看到的和看之前的想法就对应上了。
注:该问题只存在于打印引用数据类型,基本数据类型不会出现。
作者:江湖不渡i
来源:https://juejin.cn/post/7032504319584780325