คอนโซล JavaScript ของ Chrome ขี้เกียจเกี่ยวกับการประเมินอาร์เรย์หรือไม่?


129

ฉันจะเริ่มต้นด้วยรหัส:

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

ง่ายใช่มั้ย? เพื่อตอบสนองต่อสิ่งนี้ Firebug กล่าวว่า:

["hi"]
["bye"]

ยอดเยี่ยม แต่คอนโซล JavaScript ของ Chrome (7.0.517.41 เบต้า) บอกว่า:

["bye"]
["bye"]

ฉันทำอะไรผิดพลาดหรือว่าคอนโซล JavaScript ของ Chrome ขี้เกียจเป็นพิเศษในการประเมินอาร์เรย์ของฉัน

ใส่คำอธิบายภาพที่นี่


1
ฉันสังเกตพฤติกรรมเดียวกันใน Safari - อาจเป็นเรื่องของ webkit ค่อนข้างน่าแปลกใจ ฉันเรียกมันว่าแมลง
ลี

7
สำหรับฉันมันดูเหมือนบั๊ก บน Linux Opera และ Firefox จะแสดงผลตามที่คาดไว้ Chrome และเบราว์เซอร์อื่น ๆ ที่ใช้ Webkit จะไม่ทำ คุณอาจต้องการรายงานปัญหาไปยัง Webkit devs: webkit.org/quality/reporting.html
tec

2
ณ เดือนมีนาคม 2559 ปัญหานี้ไม่มีอีกแล้ว
kmonsoor

1
เมษายน 2020 มีปัญหานี้ใน Chrome เสียเวลาไป 2 ชั่วโมงเพื่อค้นหาจุดบกพร่องในโค้ดของฉันที่กลายเป็นข้อบกพร่องใน Chrome
The Fox

1
นอกจากนี้ที่น่าสังเกตว่าiคำแนะนำเครื่องมือของไอคอนสีน้ำเงินระบุว่า "มูลค่าด้านล่างได้รับการประเมินเมื่อครู่นี้"
user4642212

คำตอบ:


71

ขอบคุณสำหรับความคิดเห็น 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

1
อันที่จริงแล้วกับการเชื่อมโยงอาร์เรย์หรือวัตถุอื่น ๆ นี่อาจเป็นปัญหาที่แท้จริงเนื่องจาก toString ไม่ได้สร้างมูลค่าอะไรเลย มีวิธีแก้ไขง่ายๆสำหรับวัตถุโดยทั่วไปหรือไม่?
Eric Mickelsen

29
JSON.stringify ()
draeton

1
webkit ลงแพทช์เมื่อไม่กี่เดือนที่ผ่านมา
antony.trupe

1
ทำสิ่งนี้: console.log (JSON.parse (JSON.stringify (s));
Lee Comstock

ฉันแค่อยากจะพูดถึงว่าใน Chrome เวอร์ชันปัจจุบันคอนโซลล่าช้าและแสดงค่าผิดอีกครั้ง (หรือเคยถูก) ตัวอย่างเช่นฉันกำลังบันทึกอาร์เรย์และเปิดค่าสูงสุดหลังจากที่บันทึก แต่มันแสดงขึ้นโดยไม่มีค่าที่โผล่ขึ้นมา คำแนะนำ toString () ของคุณมีประโยชน์มากในการไปยังที่ที่ฉันต้องการเพื่อดูค่า
Nicholas

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.logคุณต้องถอดมันออกหลังจากที่คุณแก้ไขจุดบกพร่องเสร็จแล้ว


2

สิ่งนี้ได้รับการแก้ไขใน Webkit แล้วอย่างไรก็ตามเมื่อใช้ React framework สิ่งนี้เกิดขึ้นกับฉันในบางสถานการณ์หากคุณมีปัญหาดังกล่าวให้ใช้ตามที่คนอื่นแนะนำ:

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

2
สามารถยืนยัน. นี่เป็นสิ่งที่แย่ที่สุดอย่างแท้จริงเมื่อพยายามออกจากระบบ ReactSyntheticEvents แม้แต่ a JSON.parse(JSON.stringify(event))ก็ไม่ได้รับความลึก / ความแม่นยำที่ถูกต้อง ข้อความดีบักเกอร์เป็นวิธีแก้ปัญหาที่แท้จริงเพียงวิธีเดียวที่ฉันพบเพื่อให้ได้ข้อมูลเชิงลึกที่ถูกต้อง
CStumph

1

นี่เป็นคำตอบแล้ว แต่ฉันจะตอบตกลงอยู่ดี ฉันใช้เครื่องห่อคอนโซลแบบธรรมดาซึ่งไม่ประสบปัญหานี้ ต้องใช้ jQuery

จะดำเนินการเพียงlog, warnและวิธีการที่คุณจะต้องเพิ่มมากขึ้นบางในการสั่งซื้อเพื่อให้เป็นกันกับปกติerrorconsole

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;
}

นั่นเป็นสิ่งที่ดี แต่เนื่องจากเป็นสำเนาที่ตื้นจึงยังมีความเป็นไปได้ที่จะเกิดปัญหาที่ละเอียดกว่านี้ แล้ววัตถุที่ไม่ใช่อาร์เรย์ล่ะ? (นี่คือปัญหาที่แท้จริงในตอนนี้) ฉันไม่คิดว่าสิ่งที่คุณพูดเกี่ยวกับ "pre compile" จะถูกต้อง นอกจากนี้ยังมีข้อผิดพลาดในโค้ด: clone [clone.length] ควรเป็น clone [i]
Eric Mickelsen

ไม่มีข้อผิดพลาดฉันได้ดำเนินการแล้วและก็ใช้ได้ clone [clone.length] เหมือนกับ clone [i] ทุกประการเนื่องจากอาร์เรย์เริ่มต้นด้วยความยาว 0 และตัววนวนวนซ้ำ "i" ก็เช่นกัน อย่างไรก็ตามไม่แน่ใจว่ามันจะทำงานอย่างไรกับวัตถุที่ซับซ้อน แต่ IMO ก็คุ้มค่าที่จะลอง อย่างที่บอกไม่ใช่วิธีแก้ปัญหา แต่เป็นวิธีแก้ปัญหา ..
Shadow Wizard is Ear For You

@Shadow Wizard: ข้อดี: clone.length จะเท่ากับ i เสมอ มันใช้ไม่ได้กับวัตถุ บางทีอาจมีวิธีแก้ด้วย "for each"
Eric Mickelsen

วัตถุที่คุณหมายถึงนี้? var s = {param1: "hi", param2: "สบายดีไหม" }; ถ้าอย่างนั้นฉันเพิ่งทดสอบและเมื่อคุณมี s ["param1"] = "bye"; มันทำงานได้ดีตามที่คาดไว้ ช่วยโพสต์ตัวอย่าง "มันใช้ไม่ได้กับวัตถุ" ได้ไหม ฉันจะเห็นและพยายามปีนขึ้นไปเช่นกัน
Shadow Wizard is Ear For You

@ ตัวช่วยสร้างเงา: เห็นได้ชัดว่าฟังก์ชันของคุณจะไม่สามารถโคลนคุณสมบัติและจะไม่ทำงานกับวัตถุใด ๆ ที่ไม่มีคุณสมบัติความยาว จุดบกพร่องของ webkit ส่งผลกระทบต่อออบเจ็กต์ทั้งหมดไม่ใช่เฉพาะอาร์เรย์
Eric Mickelsen

0

วิธีแก้ปัญหาที่สั้นที่สุดคือการใช้อาร์เรย์หรือออบเจ็กต์การแพร่กระจายไวยากรณ์เพื่อรับโคลนของค่าที่จะเก็บรักษาไว้ในช่วงเวลาของการบันทึกเช่น:

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

อย่างไรก็ตามได้รับการเตือนเนื่องจากเป็นการคัดลอกแบบตื้นดังนั้นค่าที่ไม่ใช่แบบดั้งเดิมที่ซ้อนกันลึก ๆ จะไม่ถูกโคลนดังนั้นจึงแสดงในสถานะที่แก้ไขในคอนโซล

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.