【问题标题】:Is Chrome’s JavaScript console lazy about evaluating objects?Chrome 的 JavaScript 控制台是否懒于评估对象?
【发布时间】:2021-07-31 17:04:51
【问题描述】:

我将从代码开始:

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 控制台在评估我的数组时异常懒惰?

【问题讨论】:

  • 我在 Safari 中观察到相同的行为——所以它可能是 webkit 的东西。相当令人惊讶。我称之为错误。
  • 对我来说它看起来像一个错误。在 Linux Opera 和 Firefox 上显示预期结果,Chrome 和其他基于 Webkit 的浏览器不会。您可能希望将问题报告给 Webkit 开发人员:webkit.org/quality/reporting.html
  • 截至 2016 年 3 月,此问题已不再存在。
  • 2020 年 4 月,在 Chrome 中遇到此问题。浪费了 2 个小时在我的代码中寻找一个错误,结果证明是 Chrome 中的一个错误。
  • 另外值得注意的是,蓝色的i 图标的工具提示显示“刚刚评估了下面的值。”。

标签: javascript google-chrome javascript-objects console.log


【解决方案1】:

感谢您的评论,技术人员。我能够找到解释此问题的现有未经证实的 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

【讨论】:

  • 实际上,对于关联数组或其他对象,这可能是一个真正的问题,因为 toString 不会产生任何有价值的东西。一般对象是否有简单的解决方法?
  • webkit 几个月前为此发布了补丁
  • 这样做:console.log(JSON.parse(JSON.stringify(s));
  • 我只想提一下,在当前的 Chrome 版本中,控制台被延迟并且再次输出错误的值(或者它曾经是正确的)。例如,我正在记录一个数组并在记录后弹出顶部值,但它显示时没有弹出值。您的 toString() 建议非常有助于到达我需要查看值的位置。
  • 使用debugger; 从代码中插入断点也是一个不错的选择。 (或者如果可行的话,从开发者工具手动添加断点)。
【解决方案2】:

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

【讨论】:

  • 任何复制列表/对象的解决方案都可以使用。自 ECMAScript 2018 以来,我最喜欢的对象浅拷贝可用:copy = {...orig}
【解决方案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.logs 更慢,你必须在完成调试后将它们去掉。

【讨论】:

    【解决方案4】:

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

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

    【讨论】:

    • 可以确认。这实际上是尝试注销 ReactSyntheticEvents 时最糟糕的情况。即使是JSON.parse(JSON.stringify(event)) 也无法获得正确的深度/准确度。调试器语句是我发现的唯一能够获得正确见解的真正解决方案。
    【解决方案5】:

    到目前为止,最短的解决方案是使用数组或对象传播语法来获取要在记录时保留的值的克隆,即:

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

    但是在进行浅拷贝时会受到警告,因此任何深层嵌套的非原始值都不会被克隆,因此不会在控制台中以修改后的状态显示

    【讨论】:

      【解决方案6】:

      这已经回答了,但无论如何我都会放弃我的答案。我实现了一个不受此问题影响的简单控制台包装器。需要 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);
      

      【讨论】:

        【解决方案7】:

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

        【讨论】:

        • 这样很好,但是因为是浅拷贝,还是有可能出现更微妙的问题。那么不是数组的对象呢? (这些是现在真正的问题。)我不认为你所说的“预编译”是准确的。另外,代码中有一个错误:clone[clone.length] should be clone[i].
        • 没有错误,我已经执行过了,没问题。 clone[clone.length] 与 clone[i] 完全相同,因为数组以长度 0 开头,循环迭代器“i”也是如此。无论如何,不​​确定它对复杂对象的表现如何,但 IMO 值得一试。就像我说的那样,这不是解决方案,而是解决问题的方法..
        • @Shadow Wizard:好点:clone.length 将始终等于 i。它不适用于对象。也许有“for each”的解决方案。
        • 对象是这个意思吗? var s = { param1:“嗨”,param2:“你好吗?” };如果是这样,我刚刚测试过,当你有 s["param1"] = "bye";它按预期工作正常。您能否发布“它不适用于对象”的示例?我也会去看看并尝试攀登。
        • @Shadow Wizard:显然,您的函数将无法克隆属性,并且不会在没有长度属性的任何对象上工作。 webkit 漏洞影响所有对象,而不仅仅是数组。
        猜你喜欢
        相关资源
        最近更新 更多