Chrome 的 JavaScript 控制台在评估对象方面是否懒惰?

Is Chrome’s JavaScript console lazy about evaluating objects?

提问人:Eric Mickelsen 提问时间:10/30/2010 最后编辑:Sebastian SimonEric Mickelsen 更新时间:1/6/2022 访问量:40205

问:

我将从代码开始:

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

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

[ "hi" ]
[ "bye" ]

太好了,但 Chrome 的 JavaScript 控制台(7.0.517.41 测试版)说:

[ "bye" ]
[ "bye" ]

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

Screenshot of the console exhibiting the described behavior.

JavaScript Google-chrome JavaScript-Objects 控制台.log

评论

1赞 Lee 10/30/2010
我在 Safari 中观察到了相同的行为——所以这可能是一个 webkit 的东西。相当令人惊讶。我称之为错误。
7赞 tec 10/30/2010
对我来说,它看起来像一个错误。在 Linux 上,Opera 和 Firefox 显示预期的结果,而 Chrome 和其他基于 Webkit 的浏览器则不显示。您可能需要向 Webkit 开发人员报告该问题: webkit.org/quality/reporting.html
2赞 kmonsoor 4/1/2016
截至 2016 年 3 月,此问题已不复存在。
3赞 The Fox 4/25/2020
2020 年 4 月,在 Chrome 中出现此问题。浪费了 2 个小时在我的代码中寻找一个错误,结果证明是 Chrome 中的一个错误。
1赞 Sebastian Simon 7/13/2020
另外值得注意的是,蓝色图标的工具提示显示“刚才评估了下面的值”。i

答:

95赞 Eric Mickelsen 10/31/2010 #1

谢谢你的评论,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

评论

2赞 Eric Mickelsen 10/31/2010
实际上,对于关联数组或其他对象,这可能是一个真正的问题,因为 toString 不会产生任何有价值的东西。对于一般的对象,是否有简单的解决方法?
1赞 antony.trupe 10/9/2012
几个月前,WebKit 为此发布了一个补丁
4赞 Lee Comstock 4/11/2018
这样做: console.log(JSON.parse(JSON.stringify(s));
1赞 Nicholas R. Grant 12/6/2018
我只是想提一下,在当前的 Chrome 版本中,控制台延迟并再次输出错误的值(或者它是否正确)。例如,我正在记录一个数组并在记录后弹出最高值,但它显示时没有弹出值。您的 toString() 建议对于到达我需要查看值的位置非常有帮助。
0赞 Giorgio Tempesta 1/31/2020
从代码中插入断点也是一个不错的选择。(或者,如果可行,请从开发人员工具手动添加断点)。debugger;
2赞 Shadow Wizard Love Zelda 10/31/2010 #2

看起来 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;
}

评论

0赞 Eric Mickelsen 11/1/2010
这很好,但因为它是一个浅层的副本,所以仍然有可能出现更微妙的问题。那么不是数组的对象呢?(这些才是现在真正的问题。我不认为你所说的“预编译”是准确的。此外,代码中还有一个错误:clone[clone.length] 应该是 clone[i]。
0赞 Shadow Wizard Love Zelda 11/1/2010
没有错误,我已经执行了它,而且没问题。 clone[clone.length] 与 clone[i] 完全相同,因为数组以长度 0 开头,循环迭代器“i”也是如此。无论如何,不确定它在复杂物体上的表现如何,但 IMO 值得一试。就像我说的,这不是一个解决方案,而是解决问题的方法。
0赞 Eric Mickelsen 11/2/2010
@Shadow 向导: 好点:clone.length 将始终等于 i。它不适用于对象。也许有一个“针对每个人”的解决方案。
0赞 Shadow Wizard Love Zelda 11/2/2010
对象,你是这个意思?var s = { param1: “hi”, param2: “你好吗?” };如果是这样,我刚刚测试了,当你有 s[“param1”] = “bye”;它按预期工作正常。你能发布“它不适用于对象”的例子吗?我也会看到并尝试爬上那个。
0赞 Eric Mickelsen 11/3/2010
@Shadow向导:显然,您的函数将无法克隆属性,并且无法处理任何没有长度属性的对象。webkit 错误会影响所有对象,而不仅仅是数组。
7赞 yingted 1/28/2011 #3

您可以使用以下命令克隆数组: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

31赞 nonopolarity 10/8/2012 #4

根据 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

评论

0赞 Scar 8/23/2021
任何复制列表/对象的解决方案都将起作用。自 ECMAScript 2018 以来,我最喜欢的对象浅拷贝可用:copy = {...orig}
1赞 Krismu 8/12/2022
@Scar值得一提的是,浅拷贝会将数组变成一个对象
1赞 wrygiel 2/28/2013 #5

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

它只实现了 和方法,您必须添加更多方法才能与常规 .logwarnerrorconsole

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);
5赞 justinsAccount 4/30/2015 #6

这已在 Webkit 中修补,但是在使用 React 框架时,在某些情况下会发生这种情况,如果您遇到此类问题,请按照其他人的建议使用:

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

评论

3赞 CStumph 7/28/2015
可以确认。这在尝试注销 ReactSyntheticEvents 时实际上是最糟糕的。即使是 a 也无法获得正确的深度/精度。调试器语句是我找到的唯一真正的解决方案,可以获得正确的见解。JSON.parse(JSON.stringify(event))
2赞 ptica 4/12/2020 #7

到目前为止,最短的解决方案是使用数组或对象分布语法来获取要保留的值的克隆,就像在日志记录时一样,即:

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

但是,请注意,因为它执行浅层复制,因此不会克隆任何深度嵌套的非基元值,因此不会在控制台中以修改状态显示

评论

0赞 Radon8472 7/18/2023
好主意,但它仅适用于一级深度数据,它无法满足每个更深层次的嵌套对象/数组