Chrome 的 JavaScript 控制台是否懒于评估对象?

我从密码开始:

var s = ["hi"];
console.log(s);
s[0] = "bye";
console.log(s);

很简单,对吧? 对此,Firefox 控制台说:

[ "hi" ]
[ "bye" ]

不错,但 Chrome 的 JavaScript 控制台(7.0.517.41 beta 版)显示:

[ "bye" ]
[ "bye" ]

是我做错了什么,还是 Chrome 的 JavaScript 控制台在评估我的数组时特别懒?

Screenshot of the console exhibiting the described behavior.

37565 次浏览

谢谢你的评论 Tec。我能够找到一个现有的未经证实的 Webkit 错误,解释这个问题: https://bugs.webkit.org/show_bug.cgi?id=35801(编辑: 现在修复!)

似乎存在一些争论,关于它究竟有多大的缺陷,以及它是否可以修复。在我看来确实是不良行为。这对我来说尤其麻烦,因为至少在 Chrome 中,当代码驻留在立即执行的脚本中(在加载页面之前) ,甚至当控制台打开,页面刷新时,都会发生这种情况。当控制台尚未激活时调用 console. log 只会导致对正在排队的对象的引用,而不是控制台将包含的输出。因此,在控制台准备好之前,不会计算数组(或任何对象)。这确实是一个懒惰评估的例子。

但是,有一个简单的方法可以在代码中避免这种情况:

var s = ["hi"];
console.log(s.toString());
s[0] = "bye";
console.log(s.toString());

通过调用 toString,可以在内存中创建一个不会被以下语句更改的表示,控制台将在准备好后读取这些语句。控制台输出与直接传递对象稍有不同,但似乎是可以接受的:

hi
bye

看起来 Chrome 正在“预编译”阶段用 指针替换实际数组中的任何“ s”实例。

一种解决方法是克隆数组,而是记录新拷贝:

var s = ["hi"];
console.log(CloneArray(s));
s[0] = "bye";
console.log(CloneArray(s));


function CloneArray(array)
{
var clone = new Array();
for (var i = 0; i < array.length; i++)
clone[clone.length] = array[i];
return clone;
}

你可以用 Array#slice克隆一个数组:

console.log(s); // ["bye"], i.e. incorrect
console.log(s.slice()); // ["hi"], i.e. correct

你可以用一个函数来代替 console.log,这个函数没有这个问题,如下所示:

console.logShallowCopy = function () {
function slicedIfArray(arg) {
return Array.isArray(arg) ? arg.slice() : arg;
}


var argsSnapshot = Array.prototype.map.call(arguments, slicedIfArray);
return console.log.apply(console, argsSnapshot);
};

对于对象来说,不幸的是,最好的方法似乎是首先使用非 WebKit 浏览器进行调试,或者编写一个复杂的函数进行克隆。如果你只处理简单的对象,键的顺序并不重要,也没有函数,你可以这样做:

console.logSanitizedCopy = function () {
var args = Array.prototype.slice.call(arguments);
var sanitizedArgs = JSON.parse(JSON.stringify(args));


return console.log.apply(console, sanitizedArgs);
};

所有这些方法显然都非常慢,所以甚至比普通的 console.log更慢,在完成调试之后必须将它们去掉。

从 Eric 的解释来看,这是由于 console.log()正在排队,它将打印数组(或对象)的后一个值。

可以有5种解决方案:

1. arr.toString()   // not well for [1,[2,3]] as it shows 1,2,3
2. arr.join()       // same as above
3. arr.slice(0)     // a new array is created, but if arr is [1, 2, arr2, 3]
//   and arr2 changes, then later value might be shown
4. arr.concat()     // a new array is created, but same issue as slice(0)
5. JSON.stringify(arr)  // works well as it takes a snapshot of the whole array
//   or object, and the format shows the exact structure

这个问题已经有答案了,但是我还是会放弃我的答案。我实现了一个简单的控制台包装器,它不会受到这个问题的影响。需要 jQuery。

它只实现了 logwarnerror方法,您必须添加更多的方法,以便与常规的 console方法可以互换。

var fixedConsole;
(function($) {
var _freezeOne = function(arg) {
if (typeof arg === 'object') {
return $.extend(true, {}, arg);
} else {
return arg;
}
};
var _freezeAll = function(args) {
var frozen = [];
for (var i=0; i<args.length; i++) {
frozen.push(_freezeOne(args[i]));
}
return frozen;
};
fixedConsole = {
log: function() { console.log.apply(console, _freezeAll(arguments)); },
warn: function() { console.warn.apply(console, _freezeAll(arguments)); },
error: function() { console.error.apply(console, _freezeAll(arguments)); }
};
})(jQuery);

这已经在 Webkit 中进行了修补,但是当使用 React 框架时,在某些情况下,这种情况会发生,如果您有这样的问题,就按照其他人的建议使用:

console.log(JSON.stringify(the_array));

到目前为止最短的解决方案是使用数组或对象扩展语法来获得一个克隆的值,以便在日志记录时保留,即:

console.log({...myObject});
console.log([...myArray]);

但是,当它执行浅拷贝时会被警告,因此任何深嵌套的非原语值都不会被克隆,并因此在控制台中显示它们的修改状态