Chrome的JavaScript控制台是否懒于评估数组?


126

我将从代码开始:

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

简单吧?对此,Firebug说:

["hi"]
["bye"]

很棒,但是Chrome的JavaScript控制台(7.0.517.41 Beta)说:

["bye"]
["bye"]

我做错了什么吗?还是Chrome的JavaScript控制台在评估数组方面异常懒惰?

在此处输入图片说明


1
我在Safari中观察到了相同的行为-因此可能是Webkit的事情。非常令人惊讶。我称它为错误。

@mplungjan-不是这样。第一行声明一个“普通旧”数组,其中单个元素的索引为0。第三行仅向该元素分配一个新值。两种情况都使用简单的数字索引数组。

如果这是一个bug,为什么找不到并修复此bug超出了我的理解范围。
极性

7
对我来说,它看起来像个虫子。在Linux Opera和Firefox上显示预期结果,Chrome和其他基于Webkit的浏览器则不然。您可能需要向Webkit开发人员报告该问题:webkit.org/quality/reporting.html
tec

2
截至2016年3月,此问题不再存在。
kmonsoor

Answers:


69

感谢您的评论,技术支持。我能够找到一个现有的未确认的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

1
实际上,对于关联数组或其他对象,这可能是一个真正的问题,因为toString不会产生任何有价值的东西。一般而言,是否有一个简单的解决方法?
埃里克·米克尔森

29
JSON.stringify()
德雷顿2011年

1
Webkit在几个月前为此发布了一个补丁
antony.trupe 2012年

1
为此:console.log(JSON.parse(JSON.stringify(s));
Lee Comstock

我只是想提到,在当前的Chrome版本中,控制台被延迟并且再次输出错误值(或者曾经是正确的)。例如,我正在记录一个数组,并在记录后弹出最高值,但它没有弹出值就显示出来。您的toString()建议对于到达需要查看值的位置确实很有帮助。
Nicholas R. Grant

21

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

7

您可以使用克隆一个数组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 相比,它甚至更是如此,您必须在完成调试后将其剥离。


2

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

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

2
可以确认。实际上,这是尝试注销ReactSyntheticEvents时最糟糕的情况。甚至JSON.parse(JSON.stringify(event))没有获得正确的深度/准确性。我发现调试器语句是获得正确见解的唯一真正的解决方案。
CStumph

1

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

0

看起来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]应该为clone [i]。
埃里克·米克尔森

没错,我已经执行了,一切正常。clone [clone.length]与clone [i]完全一样,因为数组以长度0开头,因此循环迭代器“ i”也是如此。无论如何,不​​确定如何处理复杂的对象,但是IMO值得一试。就像我说的,这不是一个解决方案,这是解决问题的方式..
暗影精灵是耳朵你

@阴影向导:好点:clone.length将始终等于i。它不适用于对象。也许有一个针对“每个”的解决方案。
埃里克·米克尔森

你的意思是这个对象吗?var s = {param1:“嗨”,param2:“你好吗?” }; 如果是这样,我刚刚测试过,当你有s [“ param1”] =“ bye”; 它工作正常。您可以张贴“它不适用于对象”的示例吗?我会看到并尝试爬上那个。
影子巫师为您而耳(2010年

@Shadow向导:显然,您的函数将无法克隆属性,并且在没有length属性的任何对象上将无法使用。该webkit错误会影响所有对象,而不仅是数组。
埃里克·米克尔森

0

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

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.