ทำไมบางครั้ง setTimeout (fn, 0) จึงมีประโยชน์


872

ฉันเพิ่งพบข้อผิดพลาดค่อนข้างน่ารังเกียจรหัสนั้นโหลด<select>แบบไดนามิกผ่านทาง JavaScript โหลดแบบไดนามิกนี้<select>มีค่าที่เลือกไว้ล่วงหน้า ใน IE6 เราแล้วมีรหัสในการแก้ไขปัญหาที่เลือก<option>เพราะบางครั้ง<select>'s selectedIndexค่าจะออกจากซิงค์กับที่เลือก<option>' s indexคุณลักษณะดังต่อไปนี้:

field.selectedIndex = element.index;

อย่างไรก็ตามรหัสนี้ไม่ทำงาน แม้ว่าจะselectedIndexมีการตั้งค่าฟิลด์อย่างถูกต้อง แต่ดัชนีที่ไม่ถูกต้องก็จะถูกเลือก อย่างไรก็ตามถ้าฉันติดalert()คำสั่งในเวลาที่เหมาะสมตัวเลือกที่ถูกต้องจะถูกเลือก การคิดว่านี่อาจเป็นปัญหาเรื่องเวลาฉันลองสุ่มเลือกที่ฉันเคยเห็นในรหัสมาก่อน:

var wrapFn = (function() {
    var myField = field;
    var myElement = element;

    return function() {
        myField.selectedIndex = myElement.index;
    }
})();
setTimeout(wrapFn, 0);

และสิ่งนี้ได้ผล!

ฉันมีวิธีแก้ไขปัญหาแล้ว แต่ฉันไม่สบายใจที่ไม่รู้ว่าทำไมสิ่งนี้ถึงแก้ไขปัญหาของฉันได้ ใครบ้างมีคำอธิบายอย่างเป็นทางการ? ฉันหลีกเลี่ยงปัญหาเบราว์เซอร์ใดโดยเรียกใช้ฟังก์ชัน "ภายหลัง" โดยใช้setTimeout()?


2
คำถามส่วนใหญ่อธิบายว่าทำไมจึงมีประโยชน์ หากคุณจำเป็นต้องรู้ว่าทำไมสิ่งนี้ถึงเกิดขึ้น - อ่านคำตอบของฉัน: stackoverflow.com/a/23747597/1090562
Salvador Dali

18
Philip Roberts อธิบายสิ่งนี้ในวิธีที่ดีที่สุดเท่าที่จะเป็นไปได้ในการพูดคุยของเขาว่า youtube.com/watch?v=8aGhZQkoFbQ
Vasa

หากคุณกำลังรีบร้อนนี้เป็นส่วนหนึ่งของวิดีโอที่เขาเริ่มที่จะตอบคำถามตรง: youtu.be/8aGhZQkoFbQ?t=14m54s ไม่ว่าวิดีโอทั้งหมดจะมีค่าสำหรับดูแน่นอน
William Hampshire

4
setTimeout(fn)เหมือนกับsetTimeout(fn, 0)btw
vsync

คำตอบ:


827

ในคำถามมีสภาพการแข่งขันระหว่าง:

  1. ความพยายามของเบราว์เซอร์ในการเริ่มต้นรายการแบบหล่นลงพร้อมที่จะอัปเดตดัชนีที่เลือกและ
  2. รหัสของคุณเพื่อตั้งค่าดัชนีที่เลือก

รหัสของคุณชนะการแข่งขันนี้อย่างต่อเนื่องและพยายามตั้งค่าการเลือกแบบเลื่อนลงก่อนที่เบราว์เซอร์จะพร้อมหมายความว่าข้อผิดพลาดจะปรากฏขึ้น

การแข่งขันนี้มีอยู่เนื่องจาก JavaScript มีเธรดการทำงานเดี่ยวที่แชร์กับการแสดงผลหน้าเว็บ ผลที่ตามมาคือการเรียกใช้ JavaScript บล็อกการอัปเดตของ DOM

วิธีแก้ปัญหาของคุณคือ:

setTimeout(callback, 0)

การเรียกใช้setTimeoutด้วยการเรียกกลับและศูนย์เป็นอาร์กิวเมนต์ที่สองจะกำหนดตารางเวลาการเรียกกลับให้ทำงานแบบอะซิงโครนัสหลังจากการหน่วงเวลาที่สั้นที่สุดที่เป็นไปได้ - ซึ่งจะอยู่ที่ประมาณ 10ms เมื่อแท็บมีโฟกัส

วิธีการแก้ปัญหาของ OP คือการหน่วงเวลาประมาณ 10ms การตั้งค่าของดัชนีที่เลือก สิ่งนี้ทำให้เบราว์เซอร์มีโอกาสเริ่มต้น DOM แก้ไขข้อผิดพลาด

Internet Explorer ทุกรุ่นมีพฤติกรรมที่แปลกและการแก้ปัญหาประเภทนี้เป็นสิ่งจำเป็นในบางครั้ง หรืออาจเป็นข้อผิดพลาดของแท้ใน codebase ของ OP


ดูการพูดคุยของฟิลิปโรเบิร์ต"สิ่งที่ห่าคือห่วงเหตุการณ์?" สำหรับคำอธิบายที่ละเอียดยิ่งขึ้น


276
'วิธีแก้ไขคือ "หยุด" การเรียกใช้ JavaScript เพื่อให้เธรดการเรนเดอร์หยุดทำงาน' ไม่จริงทั้งหมดสิ่งที่ setTimeout ทำคือเพิ่มเหตุการณ์ใหม่ในคิวเหตุการณ์ของเบราว์เซอร์และเอ็นจิ้นการเรนเดอร์อยู่ในคิวนั้นแล้ว (ไม่จริงทั้งหมด แต่ใกล้พอ) จึงถูกเรียกใช้ก่อนเหตุการณ์ setTimeout
David Mulder

48
ใช่นั่นเป็นคำตอบที่ละเอียดและถูกต้องมากกว่านี้ แต่ของฉันคือ "แก้ไขให้ถูกต้อง" เพื่อให้ผู้คนเข้าใจว่าทำไมกลอุบายถึงได้ผล
Staticsan

2
@DavidMulder หมายความว่าเบราว์เซอร์แยกวิเคราะห์ css และแสดงผลในเธรดอื่นจากเธรดการเรียกใช้ javascript หรือไม่?
สัน

8
ไม่พวกเขาจะถูกแยกวิเคราะห์ในหลักการในเธรดเดียวกันมิฉะนั้นการจัดการ DOM สองสามบรรทัดจะทำให้เกิดการไหลเวียนตลอดเวลาซึ่งจะมีอิทธิพลที่ไม่ดีอย่างมากต่อความเร็วของการดำเนินการ
David Mulder

30
วิดีโอนี้เป็นคำอธิบายที่ดีที่สุดว่าทำไมเราถึงกำหนดเวลา 0 2014.jsconf.eu/speakers/…
davibq

665

คำนำ:

คำตอบอื่น ๆ บางคำตอบถูกต้อง แต่ไม่ได้แสดงให้เห็นจริง ๆ ว่าปัญหาที่แก้แล้วคืออะไรดังนั้นฉันจึงสร้างคำตอบนี้เพื่อนำเสนอภาพประกอบโดยละเอียด

เป็นเช่นนี้ฉันกำลังโพสต์รายละเอียดเดินผ่านของสิ่งที่เบราว์เซอร์ไม่และวิธีการใช้setTimeout()จะช่วยให้ มันดูค่อนข้างยาว แต่จริงๆแล้วเรียบง่ายและตรงไปตรงมา - ฉันเพิ่งทำรายละเอียดมาก

UPDATE:ฉันได้ทำเพื่อ JSFiddle สดแสดงให้เห็นถึงคำอธิบายดังต่อไปนี้: http://jsfiddle.net/C2YBE/31/ ขอบคุณมากที่ @ThangChung ที่ช่วยกันเริ่มต้นมัน

ปรับปรุง 2:ในกรณีที่เว็บไซต์ JSFiddle ตายหรือลบรหัสฉันเพิ่มรหัสในคำตอบนี้ในตอนท้าย


รายละเอียด :

ลองนึกภาพเว็บแอปด้วยปุ่ม "ทำบางสิ่ง" และ div ผลลัพธ์

onClickจัดการสำหรับปุ่ม "ทำอะไร" เรียกฟังก์ชั่น "LongCalc ()" ซึ่งจะสิ่งที่ 2:

  1. ทำการคำนวณที่ยาวมาก (บอกว่าใช้เวลา 3 นาที)

  2. พิมพ์ผลลัพธ์ของการคำนวณลงในผลลัพธ์ div

ตอนนี้ผู้ใช้ของคุณเริ่มทดสอบสิ่งนี้คลิกปุ่ม "ทำอะไรบางอย่าง" และหน้าเว็บอยู่ที่นั่นโดยไม่ทำอะไรเลยเป็นเวลา 3 นาทีพวกเขากระสับกระส่ายคลิกปุ่มอีกครั้งรอ 1 นาทีไม่มีอะไรเกิดขึ้นคลิกปุ่มอีกครั้ง ...

ปัญหาชัดเจน - คุณต้องการ "สถานะ" DIV ซึ่งแสดงว่าเกิดอะไรขึ้น เรามาดูกันว่ามันทำงานอย่างไร


ดังนั้นคุณจึงเพิ่ม "สถานะ" DIV (ตอนแรกว่างเปล่า) และแก้ไขonclickตัวจัดการ (ฟังก์ชั่นLongCalc()) เพื่อทำ 4 สิ่ง:

  1. เติมสถานะ "กำลังคำนวณ ... อาจใช้เวลาประมาณ 3 นาที" ในสถานะ DIV

  2. ทำการคำนวณที่ยาวมาก (บอกว่าใช้เวลา 3 นาที)

  3. พิมพ์ผลลัพธ์ของการคำนวณลงในผลลัพธ์ div

  4. เติมสถานะ "การคำนวณเสร็จแล้ว" ลงในสถานะ DIV

และคุณยินดีให้แอปแก่ผู้ใช้ในการทดสอบอีกครั้ง

พวกเขากลับมาหาคุณด้วยความโกรธ และอธิบายว่าเมื่อพวกเขาคลิกปุ่มสถานะ DIV จะไม่อัปเดตด้วยสถานะ "กำลังคำนวณ ... " !!!


คุณเกาหัวถามเกี่ยวกับ StackOverflow (หรืออ่านเอกสารหรือ Google) และตระหนักถึงปัญหา:

เบราว์เซอร์ทุกสถานที่ของ "สิ่งที่ต้องทำ" งาน (งาน UI ทั้งสองและคำสั่งจาวาสคริปต์) เป็นผลมาจากเหตุการณ์ที่เกิดขึ้นเป็นคิวเดียว และน่าเสียดายที่การวาดใหม่ "สถานะ" DIV ด้วยค่า "กำลังคำนวณ ... " ใหม่เป็นสิ่งที่ต้องทำแยกต่างหากซึ่งจะไปยังจุดสิ้นสุดของคิว!

นี่คือรายละเอียดของกิจกรรมในระหว่างการทดสอบของผู้ใช้เนื้อหาของคิวหลังจากแต่ละเหตุการณ์:

  • คิว: [Empty]
  • กิจกรรม: คลิกที่ปุ่ม คิวหลังจากเหตุการณ์:[Execute OnClick handler(lines 1-4)]
  • เหตุการณ์: เรียกใช้บรรทัดแรกในตัวจัดการ OnClick (เช่นเปลี่ยนค่าสถานะ DIV) [Execute OnClick handler(lines 2-4), re-draw Status DIV with new "Calculating" value]คิวหลังจากเหตุการณ์: โปรดทราบว่าขณะที่การเปลี่ยนแปลง DOM เกิดขึ้นทันทีอีกครั้งวาดองค์ประกอบที่สอดคล้องกัน DOM คุณจำเป็นต้องมีเหตุการณ์ใหม่ที่เกิดจากการเปลี่ยนแปลง DOM, ที่ไปในตอนท้ายของคิว
  • ปัญหา!!! ปัญหา!!! รายละเอียดอธิบายด้านล่าง
  • เหตุการณ์: เรียกใช้บรรทัดที่สองในตัวจัดการ (การคำนวณ) [Execute OnClick handler(lines 3-4), re-draw Status DIV with "Calculating" value]คิวหลัง:
  • เหตุการณ์: เรียกใช้บรรทัดที่ 3 ในตัวจัดการ (เติมผลลัพธ์ DIV) [Execute OnClick handler(line 4), re-draw Status DIV with "Calculating" value, re-draw result DIV with result]คิวหลัง:
  • เหตุการณ์: ดำเนินการบรรทัดที่ 4 ในตัวจัดการ (ระบุสถานะ DIV ด้วย "DONE") [Execute OnClick handler, re-draw Status DIV with "Calculating" value, re-draw result DIV with result; re-draw Status DIV with "DONE" value]คิว:
  • เหตุการณ์: ดำเนินการโดยนัยreturnจากonclickตัวจัดการย่อย เรานำ "Execute OnClick handler" ออกจากคิวและเริ่มดำเนินการรายการถัดไปในคิว
  • หมายเหตุ: เนื่องจากเราทำการคำนวณเสร็จแล้ว 3 นาทีจึงส่งให้กับผู้ใช้แล้ว กิจกรรมวาดซ้ำยังไม่เกิดขึ้น !!!
  • กิจกรรม: วาดสถานะใหม่ DIV ด้วยค่า "กำลังคำนวณ" เราทำการวาดใหม่และถอดมันออกจากคิว
  • เหตุการณ์: วาด DIV ผลลัพธ์อีกครั้งด้วยค่าผลลัพธ์ เราทำการวาดใหม่และถอดมันออกจากคิว
  • กิจกรรม: วาดสถานะใหม่ DIV ด้วยค่า "เสร็จสิ้น" เราทำการวาดใหม่และถอดมันออกจากคิว ผู้ชมที่มีตาเฉียบอาจสังเกตเห็น "สถานะ DIV ด้วยค่า" การคำนวณ "ที่กระพริบสำหรับเศษเสี้ยวของไมโครวินาที - หลังจากเสร็จสิ้นการคำนวณ

ดังนั้นปัญหาพื้นฐานคือเหตุการณ์ re-draw สำหรับ "สถานะ" DIV ถูกวางในคิวในตอนท้ายหลังจากเหตุการณ์ "execute line 2" ซึ่งใช้เวลา 3 นาทีดังนั้นการวาดใหม่จริงจะไม่เกิดขึ้นจนกว่า หลังจากการคำนวณเสร็จสิ้น


setTimeout()เพื่อช่วยเหลือมา มันช่วยได้อย่างไร เพราะด้วยการเรียกใช้โค้ดที่ทำงานแบบยาวผ่านทางsetTimeoutจริง ๆ คุณสร้าง 2 เหตุการณ์: setTimeoutการดำเนินการเองและ (เนื่องจากหมดเวลา 0), รายการคิวแยกต่างหากสำหรับรหัสที่กำลังดำเนินการ

ดังนั้นเพื่อแก้ไขปัญหาของคุณคุณแก้ไขonClickhandler ของคุณให้เป็นคำสั่ง TWO (ในฟังก์ชั่นใหม่หรือเพียงแค่บล็อกภายในonClick):

  1. เติมสถานะ "กำลังคำนวณ ... อาจใช้เวลาประมาณ 3 นาที" ในสถานะ DIV

  2. ดำเนินการsetTimeout()ด้วย 0 หมดเวลาและโทรออกไปยังLongCalc()ฟังก์ชั่น

    LongCalc()ฟังก์ชั่นเกือบเหมือนครั้งที่แล้ว แต่เห็นได้ชัดว่าไม่มีสถานะ "กำลังคำนวณ ... " การอัปเดต DIV เป็นขั้นตอนแรก และเริ่มการคำนวณแทนทันที

ดังนั้นลำดับเหตุการณ์และคิวจะเป็นอย่างไรตอนนี้

  • คิว: [Empty]
  • กิจกรรม: คลิกที่ปุ่ม คิวหลังจากเหตุการณ์:[Execute OnClick handler(status update, setTimeout() call)]
  • เหตุการณ์: เรียกใช้บรรทัดแรกในตัวจัดการ OnClick (เช่นเปลี่ยนค่าสถานะ DIV) [Execute OnClick handler(which is a setTimeout call), re-draw Status DIV with new "Calculating" value]คิวหลังจากเหตุการณ์:
  • เหตุการณ์: เรียกใช้บรรทัดที่สองในตัวจัดการ (การโทร setTimeout) [re-draw Status DIV with "Calculating" value]คิวหลัง: คิวไม่มีอะไรใหม่อยู่ในนั้นอีก 0 วินาที
  • เหตุการณ์: การเตือนภัยจากการหมดเวลาใช้งานจะดับลง 0 วินาทีต่อมา [re-draw Status DIV with "Calculating" value, execute LongCalc (lines 1-3)]คิวหลัง:
  • เหตุการณ์: ใหม่วาด DIV สถานะด้วย "คำนวณ" ค่า [execute LongCalc (lines 1-3)]คิวหลัง: โปรดทราบว่าเหตุการณ์วาดใหม่นี้อาจเกิดขึ้นจริงก่อนที่สัญญาณเตือนจะดับซึ่งทำงานได้ดีเช่นกัน
  • ...

ไชโย! Status DIV เพิ่งได้รับการอัปเดตเป็น "กำลังคำนวณ ... " ก่อนการคำนวณเริ่ม !!!



ด้านล่างเป็นตัวอย่างโค้ดจาก JSFiddle ที่แสดงตัวอย่างเหล่านี้: http://jsfiddle.net/C2YBE/31/ :

รหัส HTML:

<table border=1>
    <tr><td><button id='do'>Do long calc - bad status!</button></td>
        <td><div id='status'>Not Calculating yet.</div></td>
    </tr>
    <tr><td><button id='do_ok'>Do long calc - good status!</button></td>
        <td><div id='status_ok'>Not Calculating yet.</div></td>
    </tr>
</table>

รหัส JavaScript: (ดำเนินการonDomReadyและอาจต้องใช้ jQuery 1.9)

function long_running(status_div) {

    var result = 0;
    // Use 1000/700/300 limits in Chrome, 
    //    300/100/100 in IE8, 
    //    1000/500/200 in FireFox
    // I have no idea why identical runtimes fail on diff browsers.
    for (var i = 0; i < 1000; i++) {
        for (var j = 0; j < 700; j++) {
            for (var k = 0; k < 300; k++) {
                result = result + i + j + k;
            }
        }
    }
    $(status_div).text('calculation done');
}

// Assign events to buttons
$('#do').on('click', function () {
    $('#status').text('calculating....');
    long_running('#status');
});

$('#do_ok').on('click', function () {
    $('#status_ok').text('calculating....');
    // This works on IE8. Works in Chrome
    // Does NOT work in FireFox 25 with timeout =0 or =1
    // DOES work in FF if you change timeout from 0 to 500
    window.setTimeout(function (){ long_running('#status_ok') }, 0);
});

12
คำตอบที่ดี DVK! นี่คือส่วนสำคัญที่แสดงตัวอย่างgist.github.com/kumikoda/5552511#file-timeout-html
kumikoda

4
คำตอบที่ยอดเยี่ยมจริงๆ DVK เพียงเพื่อให้ง่ายต่อการจินตนาการฉันได้ใส่รหัสนั้นเพื่อ jsfiddle jsfiddle.net/thangchung/LVAaV
thangchung

4
@ThangChung - ฉันพยายามทำให้เป็นรุ่นที่ดีขึ้น (2 ปุ่มสำหรับแต่ละกรณี) ใน JSFiddle การทำงานเป็นสาธิตบน Chrome และ IE แต่ไม่ได้อยู่ใน FF ด้วยเหตุผลบางอย่าง - ดูjsfiddle.net/C2YBE/31 ฉันถามว่าเหตุใด FF จึงไม่ทำงานที่นี่: stackoverflow.com/questions/20747591/…
DVK

1
@DVK "เบราว์เซอร์วางภารกิจ" สิ่งที่ต้องทำ "ทั้งหมด (ทั้งงาน UI และคำสั่ง JavaScript) ซึ่งเป็นผลมาจากเหตุการณ์ต่างๆเข้าสู่คิวเดียว" ท่านช่วยกรุณาจัดหาแหล่งที่มาสำหรับสิ่งนี้ได้ไหม Imho arent เบราว์เซอร์ควรมี UI (
เอ็นจิ้

1
@bhavya_w ไม่ทุกอย่างเกิดขึ้นในหนึ่งเธรด นี่คือเหตุผลที่การคำนวณ js ที่ยาวนานสามารถบล็อก UI
Seba Kerckhof

88

ลองดูที่จอห์นเรซิกบทความของเกี่ยวกับวิธี JavaScript จับเวลาการทำงาน เมื่อคุณตั้งค่าการหมดเวลาจริง ๆ แล้วมันจะรอคิวรหัสแบบอะซิงโครนัสจนกว่าเอ็นจินจะเรียกใช้สแตกการโทรปัจจุบัน


23

setTimeout() ซื้อให้คุณจนกว่าจะมีการโหลดองค์ประกอบ DOM แม้ว่าจะถูกตั้งค่าเป็น 0

ลองดูสิ: setTimeout


23

เบราว์เซอร์ส่วนใหญ่มีกระบวนการที่เรียกว่าเธรดหลักซึ่งรับผิดชอบในการดำเนินงาน JavaScript บางอย่างการอัปเดต UI เช่น: การวาดภาพการวาดซ้ำหรือการรีโฟลว์ ฯลฯ

งานจาวาสคริปต์และการอัปเดต UI บางงานจะถูกจัดคิวไว้ในคิวข้อความของเบราว์เซอร์จากนั้นจะถูกส่งไปยังเธรดหลักของเบราว์เซอร์ที่จะดำเนิน

เมื่อสร้างการอัพเดต UI ในขณะที่เธรดหลักไม่ว่างงานจะถูกเพิ่มลงในคิวข้อความ

setTimeout(fn, 0);เพิ่มสิ่งนี้fnไปยังจุดสิ้นสุดของคิวที่จะดำเนินการ มันกำหนดเวลางานที่จะเพิ่มในคิวข้อความหลังจากระยะเวลาที่กำหนด


"ภารกิจการเรียกใช้ JavaScript และการอัปเดต UI ทุกครั้งจะถูกเพิ่มไปยังระบบคิวเหตุการณ์ของเบราว์เซอร์ดังนั้นงานเหล่านั้นจะถูกส่งไปยัง UI เธรดหลักของเบราว์เซอร์ที่จะดำเนินการ" แหล่งที่มาโปรด?
bhavya_w

4
JavaScript ประสิทธิภาพสูง (นิโคลัสซากัส, สโตยันสเตฟานอฟ, รอสส์ฮาร์มส์, ม็องเชียนโคลอมเต้และแมตต์สวีนีย์)
Arley

downvote add this fn to the end of the queueสำหรับเรื่องนี้ สิ่งที่สำคัญที่สุดคือการsetTimeoutเพิ่ม func นี้จุดสิ้นสุดของวงรอบนี้หรือเริ่มรอบวงถัดไป
สีเขียว

19

มีคำตอบ upvoted ที่ขัดแย้งกันอยู่ที่นี่และโดยไม่มีข้อพิสูจน์ไม่มีวิธีที่จะรู้ว่าใครจะเชื่อ นี่คือหลักฐานว่า @DVK ถูกต้องและ @SalvadorDali ไม่ถูกต้อง การเรียกร้องหลัง:

"และนี่คือสาเหตุ: เป็นไปไม่ได้ที่จะมี setTimeout ด้วยการหน่วงเวลาเป็น 0 มิลลิวินาทีค่าขั้นต่ำจะถูกกำหนดโดยเบราว์เซอร์และไม่ได้เป็น 0 มิลลิวินาทีในอดีตเบราว์เซอร์ตั้งค่าขั้นต่ำนี้เป็น 10 มิลลิวินาที เบราว์เซอร์สมัยใหม่ตั้งค่าไว้ที่ 4 มิลลิวินาที "

การหมดเวลาขั้นต่ำ 4ms ไม่เกี่ยวข้องกับสิ่งที่เกิดขึ้น สิ่งที่เกิดขึ้นจริง ๆ คือ setTimeout จะส่งฟังก์ชันการเรียกกลับไปยังจุดสิ้นสุดของคิวการดำเนินการ หากหลังจาก setTimeout (callback, 0) คุณมีรหัสการปิดกั้นซึ่งใช้เวลาหลายวินาทีในการทำงานการโทรกลับจะไม่ถูกดำเนินการเป็นเวลาหลายวินาทีจนกว่ารหัสการบล็อกจะเสร็จสิ้น ลองรหัสนี้:

function testSettimeout0 () {
    var startTime = new Date().getTime()
    console.log('setting timeout 0 callback at ' +sinceStart())
    setTimeout(function(){
        console.log('in timeout callback at ' +sinceStart())
    }, 0)
    console.log('starting blocking loop at ' +sinceStart())
    while (sinceStart() < 3000) {
        continue
    }
    console.log('blocking loop ended at ' +sinceStart())
    return // functions below
    function sinceStart () {
        return new Date().getTime() - startTime
    } // sinceStart
} // testSettimeout0

ผลลัพธ์คือ:

setting timeout 0 callback at 0
starting blocking loop at 5
blocking loop ended at 3000
in timeout callback at 3033

คำตอบของคุณไม่ได้พิสูจน์อะไร มันแสดงให้เห็นว่าในเครื่องของคุณภายใต้ในสถานการณ์เฉพาะคอมพิวเตอร์ทำให้คุณมีตัวเลข ในการพิสูจน์สิ่งที่เกี่ยวข้องคุณต้องมีโค้ดมากกว่าสองสามบรรทัดและตัวเลขสองสามตัว
Salvador Dali

6
@SalvadorDali ฉันเชื่อว่าหลักฐานของฉันชัดเจนเพียงพอสำหรับคนส่วนใหญ่ที่จะเข้าใจ ฉันคิดว่าคุณรู้สึกว่าได้รับการป้องกันและไม่ได้พยายามทำความเข้าใจ ฉันยินดีที่จะพยายามอธิบาย แต่ฉันไม่ทราบว่าคุณไม่เข้าใจอะไร ลองใช้รหัสในเครื่องของคุณเองหากคุณสงสัยว่าผลลัพธ์ของฉัน
Vladimir Kornea

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

15

เหตุผลหนึ่งที่ทำเช่นนั้นคือการเลื่อนการทำงานของรหัสไปยังลูปเหตุการณ์ที่แยกต่างหากและตามมา เมื่อตอบสนองต่อเหตุการณ์เบราว์เซอร์บางประเภท (เช่นการคลิกเมาส์) บางครั้งก็จำเป็นต้องดำเนินการหลังจากที่ประมวลผลเหตุการณ์ปัจจุบันแล้วเท่านั้น setTimeout()สิ่งอำนวยความสะดวกเป็นวิธีที่ง่ายที่จะทำมัน

แก้ไขตอนนี้เป็นปี 2558 ฉันควรทราบด้วยว่ามีอะไรrequestAnimationFrame()บ้างที่ไม่เหมือนกัน แต่ก็ใกล้พอsetTimeout(fn, 0)ที่จะพูดถึง


นี่เป็นหนึ่งในสถานที่ที่ฉันเคยเห็นมันถูกใช้อย่างแม่นยำ =)
Zaibot

FYI: คำตอบนี้ถูกรวมเข้าที่นี่จากstackoverflow.com/questions/4574940/…
Shog9

2
คือการเลื่อนการเรียกใช้โค้ดไปยังลูปเหตุการณ์ที่ตามมาแยกต่างหาก : คุณจะหาลูปเหตุการณ์ที่ตามมาได้อย่างไร คุณจะคิดออกว่าเหตุการณ์วงปัจจุบันคืออะไร? คุณจะรู้ได้อย่างไรว่าเหตุการณ์ใดทำให้คุณเป็นห่วง
กรีน

@ กรีนดีคุณทำไม่ได้จริงๆ ไม่มีการเปิดเผยโดยตรงถึงสิ่งที่รันไทม์ของ JavaScript
Pointy

RequestAnimationFrame แก้ไขปัญหาที่ฉันมีกับ IE และ Firefox ที่ไม่ได้อัปเดต UI บางครั้ง
David

9

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

ดังนั้นคุณมีสองฟังก์ชั่น:

var f1 = function () {    
   setTimeout(function(){
      console.log("f1", "First function call...");
   }, 0);
};

var f2 = function () {
    console.log("f2", "Second call...");
};

แล้วเรียกพวกเขาตามลำดับต่อไปนี้f1(); f2();เพียงเพื่อดูว่าคนที่สองดำเนินการครั้งแรก

และนี่คือสาเหตุ: เป็นไปไม่ได้ที่จะมีsetTimeoutความล่าช้าเป็น 0 มิลลิวินาที มูลค่าขั้นต่ำที่จะถูกกำหนดโดยเบราว์เซอร์และมันไม่ได้เป็นมิลลิวินาที 0 ในอดีตเบราว์เซอร์จะตั้งค่าขั้นต่ำนี้ไว้ที่ 10 มิลลิวินาที แต่ข้อกำหนดของ HTML5และเบราว์เซอร์รุ่นใหม่ได้ตั้งไว้ที่ 4 มิลลิวินาที

หากระดับการซ้อนมากกว่า 5 และการหมดเวลาน้อยกว่า 4 ให้เพิ่มการหมดเวลาเป็น 4

ยังมาจาก mozilla:

ในการดำเนินการหมดเวลา 0 มิลลิวินาทีในเบราว์เซอร์ที่ทันสมัยคุณสามารถใช้ window.postMessage () ตามที่อธิบายไว้ที่นี่

ข้อมูล PS จะนำมาหลังจากที่ได้อ่านต่อไปนี้บทความ


1
@ user2407309 คุณล้อเล่นไหม? คุณหมายถึงว่าข้อกำหนดของ HTML5 ผิดและถูกต้องหรือไม่ อ่านแหล่งข้อมูลก่อนที่คุณจะลงคะแนนและทำการอ้างสิทธิ์ที่ชัดเจน คำตอบของฉันขึ้นอยู่กับข้อมูลจำเพาะ HTML และบันทึกประวัติศาสตร์ แทนที่จะทำคำตอบซึ่งอธิบายสิ่งเดียวกันซ้ำแล้วซ้ำเล่าฉันเพิ่มสิ่งใหม่สิ่งที่ไม่ได้แสดงในคำตอบก่อนหน้า ฉันไม่ได้บอกว่านี่คือเหตุผลเดียวที่ฉันเพิ่งแสดงอะไรใหม่
Salvador Dali

1
สิ่งนี้ไม่ถูกต้อง: "และนี่คือสาเหตุ: เป็นไปไม่ได้ที่จะมี setTimeout โดยมีการหน่วงเวลาเป็น 0 มิลลิวินาที" นั่นไม่ใช่เหตุผล ความล่าช้า 4ms ไม่เกี่ยวข้องกับสาเหตุที่setTimeout(fn,0)มีประโยชน์
Vladimir Kornea

@ user2407309 มันสามารถแก้ไขได้อย่างง่ายดายเพื่อ "เพื่อเพิ่มเหตุผลที่ระบุโดยคนอื่นมันเป็นไปไม่ได้ .... " ดังนั้นมันก็ไร้สาระที่จะลงคะแนนเพียงเพราะเรื่องนี้โดยเฉพาะอย่างยิ่งถ้าคุณตอบเองไม่ได้บอกอะไรใหม่ การแก้ไขเพียงเล็กน้อยก็พอเพียงแล้ว
Salvador Dali

10
ซัลวาดอร์ดาลี: ถ้าคุณไม่สนใจแง่มุมทางอารมณ์ของสงครามไมโครไฟที่นี่คุณอาจต้องยอมรับว่า @VladimirKornea ถูกต้อง มันเป็นความจริงที่เบราว์เซอร์แมปล่าช้า 0ms ถึง 4 มิลลิวินาที แต่แม้ว่าพวกเขาจะไม่ได้ผลลัพธ์ก็จะยังคงเหมือนเดิม กลไกการขับรถที่นี่คือรหัสที่ถูกผลักลงคิวแทนที่จะเป็นกองการโทร ลองดูที่การนำเสนอ JSConf ที่ยอดเยี่ยมนี้มันอาจช่วยชี้แจงปัญหา: youtube.com/watch?v=8aGhZQkoFbQ
hashchange

1
ฉันสับสนว่าทำไมคุณคิดว่าการเสนอราคาต่ำสุดที่มีคุณสมบัติเท่ากับ 4 ms คือราคาต่ำสุดทั่วโลกที่ 4 มิลลิวินาที ตามใบเสนอราคาของคุณจากข้อมูลจำเพาะ HTML5 แสดงว่าขั้นต่ำคือ 4 ms เฉพาะเมื่อคุณซ้อนการเรียกไปยังsetTimeout/ setIntervalมากกว่าห้าระดับ ถ้าคุณยังไม่มีขั้นต่ำคือ 0 ms (สิ่งที่ขาดเวลาเครื่องจักร) เอกสารของ Mozilla ขยายออกเพื่อให้ครอบคลุมซ้ำไม่เพียง แต่กรณีซ้อน (ดังนั้นsetIntervalด้วยช่วงเวลา 0 จะกำหนดตารางเวลาใหม่อีกครั้งสองสามครั้งจากนั้นล่าช้าอีกต่อไปหลังจากนั้น) แต่การใช้งานแบบง่าย ๆsetTimeoutกับการวางซ้อนขั้นต่ำ
ShadowRanger

8

เนื่องจากมันจะถูกส่งผ่านช่วงเวลาของ0ฉันคิดว่ามันเป็นเพื่อที่จะลบรหัสผ่านไปsetTimeoutจากการไหลของการดำเนินการ ดังนั้นถ้ามันเป็นฟังก์ชั่นที่อาจใช้เวลาสักครู่มันจะไม่ป้องกันโค้ดที่ตามมาจากการเรียกใช้งาน


FYI: คำตอบนี้ถูกรวมเข้าที่นี่จากstackoverflow.com/questions/4574940/…
Shog9

7

คำตอบที่ได้รับคะแนนสูงสุดทั้งสองนี้ผิด ลองดูคำอธิบาย MDN เกี่ยวกับรูปแบบการทำงานพร้อมกันและวนรอบเหตุการณ์และควรจะชัดเจนว่าเกิดอะไรขึ้น (ทรัพยากร MDN นั้นเป็นอัญมณีจริง) และการใช้เพียงแค่ setTimeoutสามารถเพิ่มปัญหาที่ไม่คาดคิดในรหัสของคุณนอกเหนือจาก "การแก้" ปัญหาเล็กน้อยนี้

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

jsfiddleให้โดย DVK จริงแสดงให้เห็นถึงปัญหา แต่คำอธิบายของเขามันไม่ถูกต้อง

สิ่งที่เกิดขึ้นในรหัสของเขาก็คือเขาเป็นคนแรกที่แนบตัวจัดการclickเหตุการณ์กับเหตุการณ์ที่#doปุ่ม

จากนั้นเมื่อคุณจริงคลิกที่ปุ่มที่ถูกสร้างขึ้นอ้างอิงฟังก์ชั่นการจัดการเหตุการณ์ที่ได้รับเพิ่มไปยังmessage message queueเมื่อevent loopถึงข้อความนี้มันจะสร้างframeสแต็คโดยมีการเรียกใช้ฟังก์ชันไปยังตัวจัดการเหตุการณ์คลิกใน jsfiddle

และนี่คือจุดที่น่าสนใจ เราคุ้นเคยกับการใช้ Javascript ว่าเป็นแบบอะซิงโครนัสซึ่งเรามักจะมองข้ามข้อเท็จจริงเล็ก ๆ นี้: เฟรมใด ๆ ที่จะต้องถูกดำเนินการโดยสมบูรณ์ก่อนที่เฟรมถัดไปจะสามารถดำเนินการได้ ไม่มีคนเห็นพ้องด้วย

สิ่งนี้หมายความว่า? หมายความว่าเมื่อใดก็ตามที่มีการเรียกใช้ฟังก์ชั่นจากคิวข้อความมันจะบล็อคคิวจนกว่าสแต็กที่สร้างขึ้นจะถูกลบออก หรือในแง่ทั่วไปมันจะบล็อกจนกว่าฟังก์ชันจะกลับมา และจะบล็อกทุกอย่างรวมถึงการดำเนินการแสดง DOM การเลื่อนและการบันทึก หากคุณต้องการการยืนยันให้ลองเพิ่มระยะเวลาของการดำเนินการที่ต้องใช้เวลานานในซอ ถ้ามันทำงานได้นานพอเบราว์เซอร์ของคุณจะถามคุณว่าคุณต้องการที่จะฆ่ากระบวนการหรือไม่เพราะมันทำให้หน้าเว็บไม่ตอบสนอง กำลังดำเนินการเฟรมและเหตุการณ์ลูปและคิวข้อความค้างอยู่จนกว่าจะเสร็จสิ้น

เหตุใดผลข้างเคียงของข้อความจึงไม่อัปเดต เพราะในขณะที่คุณได้เปลี่ยนแปลงมูลค่าขององค์ประกอบใน DOM ที่ - คุณสามารถconsole.log()คุ้มค่าคุ้มทันทีหลังจากเปลี่ยนมันและดูว่ามันได้รับการเปลี่ยนแปลง (ซึ่งแสดงให้เห็นว่าทำไมคำอธิบาย DVK ของไม่ถูกต้อง) - เบราว์เซอร์ที่กำลังรอสำหรับสแต็คที่จะหมดสิ้นลง ( onฟังก์ชั่นการจัดการเพื่อกลับ) และทำให้ข้อความเสร็จสิ้นดังนั้นในที่สุดมันก็สามารถดำเนินการกับข้อความที่ถูกเพิ่มโดยรันไทม์เป็นปฏิกิริยาต่อการดำเนินการกลายพันธุ์ของเราและเพื่อสะท้อนการกลายพันธุ์ใน UI .

นี่เป็นเพราะเรากำลังรอรหัสเพื่อให้เสร็จสิ้นการทำงาน เราไม่ได้พูดว่า "มีคนดึงข้อมูลนี้แล้วเรียกใช้ฟังก์ชันนี้พร้อมผลลัพธ์ขอบคุณและตอนนี้ฉันกลับมาแล้ว imma ทำอะไรก็ได้ตอนนี้" เหมือนปกติที่เราทำกับ Javascript แบบอะซิงโครนัสของเรา เราเข้าสู่ฟังก์ชั่นตัวจัดการเหตุการณ์คลิกเราอัปเดตองค์ประกอบ DOM เราเรียกฟังก์ชั่นอื่นฟังก์ชั่นอื่นใช้งานได้นานแล้วกลับมาจากนั้นอัปเดตองค์ประกอบ DOM เดียวกันจากนั้นเรากลับมาจากฟังก์ชั่นเริ่มต้น กองซ้อน และจากนั้นเบราว์เซอร์จะได้รับข้อความถัดไปในคิวซึ่งอาจเป็นอย่างดีจะสร้างข้อความโดยเราโดยเรียกภายใน "ใน-DOM-กลายพันธุ์" เหตุการณ์บางชนิด

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

ทำไมsetTimeoutสิ่งนี้ถึงได้ผล มันทำเช่นนั้นเพราะมันจะลบการเรียกไปยังฟังก์ชั่นที่ใช้เวลานานจากเฟรมของตัวเองอย่างมีประสิทธิภาพการกำหนดเวลาที่จะดำเนินการในภายหลังในwindowบริบทเพื่อให้ตัวเองสามารถกลับทันทีและอนุญาตให้คิวข้อความในการประมวลผลข้อความอื่น ๆ และแนวคิดก็คือข้อความ UI "ที่อัปเดต" ที่ถูกเรียกโดยเราใน Javascript เมื่อเปลี่ยนข้อความใน DOM อยู่ข้างหน้าข้อความที่อยู่ในคิวสำหรับฟังก์ชั่นที่ใช้งานยาวนานเพื่อให้การอัปเดต UI เกิดขึ้นก่อนที่เราจะปิดกั้น เป็นเวลานาน.

โปรดทราบว่า a) ฟังก์ชั่นที่ใช้เวลานานยังคงปิดกั้นทุกอย่างเมื่อมันทำงานและ b) คุณไม่รับประกันว่าการอัปเดต UI นั้นจะอยู่ข้างหน้าในคิวข้อความ ในเบราว์เซอร์ Chrome มิถุนายน 2018 ของฉันค่า0ไม่ได้ "แก้ไข" ปัญหาที่ซอแสดงให้เห็น - 10 ทำ จริง ๆ แล้วฉันรู้สึกอึดอัดใจเล็กน้อยเนื่องจากดูเหมือนว่าฉันมีเหตุผลว่าข้อความอัปเดต UI ควรเข้าคิวก่อนหน้านี้เนื่องจากมีการเรียกใช้งานทริกเกอร์ก่อนกำหนดเวลาฟังก์ชั่นที่รันเป็นเวลานานเพื่อให้เรียกใช้ "ภายหลัง" แต่บางทีอาจมีการปรับแต่งให้เหมาะสมในเอ็นจิ้น V8 ซึ่งอาจรบกวนหรือบางทีความเข้าใจของฉันก็ไม่เพียงพอ

โอเคปัญหาเกี่ยวกับการใช้setTimeoutคืออะไรและอะไรคือวิธีแก้ปัญหาที่ดีกว่าสำหรับกรณีนี้

ก่อนอื่นปัญหาเกี่ยวกับการใช้setTimeoutตัวจัดการเหตุการณ์เช่นนี้เพื่อพยายามที่จะบรรเทาปัญหาอื่น ๆ มีแนวโน้มที่จะสับสนกับรหัสอื่น ๆ นี่คือตัวอย่างจริงจากงานของฉัน:

เพื่อนร่วมงานมีความเข้าใจผิดเกี่ยวกับเหตุการณ์วนรอบพยายามที่จะ "เธรด" Javascript โดยใช้รหัสการสร้างเทมเพลตสำหรับใช้setTimeout 0ในการแสดงผล เขาไม่อยู่ที่นี่เพื่อถามอีกต่อไป แต่ฉันสามารถสันนิษฐานได้ว่าบางทีเขาอาจแทรกตัวจับเวลาเพื่อวัดความเร็วในการเรนเดอร์ (ซึ่งจะเป็นการกลับมาของฟังก์ชั่นที่รวดเร็ว) และพบว่าการใช้วิธีนี้จะตอบสนองอย่างรวดเร็ว

ปัญหาแรกชัดเจน คุณไม่สามารถด้าย javascript ได้ดังนั้นคุณจะไม่ชนะอะไรที่นี่ในขณะที่เพิ่ม obfuscation ประการที่สองตอนนี้คุณได้แยกการแสดงผลเทมเพลตออกจากสแต็คของผู้ฟังเหตุการณ์ที่เป็นไปได้อย่างมีประสิทธิภาพซึ่งอาจคาดว่าเทมเพลตนั้นจะถูกเรนเดอร์ในขณะที่มันอาจจะไม่ค่อยดีนัก พฤติกรรมที่แท้จริงของฟังก์ชั่นนี้ไม่ได้ถูกกำหนดขึ้นอย่างที่เคยเป็นมา - โดยไม่รู้ตัว - ฟังก์ชั่นใด ๆ ที่จะรันหรือขึ้นอยู่กับมัน คุณสามารถเดาได้อย่างถูกต้อง แต่ไม่สามารถเขียนโค้ดสำหรับพฤติกรรมของมันได้

"แก้ไข" เมื่อเขียนตัวจัดการเหตุการณ์ใหม่ที่ขึ้นอยู่กับตรรกะของก็จะยังsetTimeout 0ใช้ แต่นั่นไม่ใช่การแก้ไขมันยากที่จะเข้าใจและไม่สนุกที่จะแก้ไขข้อผิดพลาดที่เกิดจากรหัสเช่นนี้ บางครั้งก็ไม่มีปัญหาบางครั้งก็ล้มเหลวอย่างต่อเนื่องและอีกครั้งบางครั้งก็ทำงานและหยุดพักเป็นระยะ ๆ ขึ้นอยู่กับประสิทธิภาพปัจจุบันของแพลตฟอร์มและสิ่งที่เกิดขึ้นในเวลา นี่คือเหตุผลที่ฉันแนะนำให้ใช้แฮ็คนี้เป็นการส่วนตัว (มันคือแฮ็คและเราทุกคนควรรู้ว่ามันคือ) เว้นแต่คุณจะรู้ว่าคุณกำลังทำอะไรอยู่และผลที่ตามมาคืออะไร

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

โอ้และถ้าคุณคิดว่า "เอาล่ะฉันจะไม่โทรกลับไปที่ฟังก์ชั่นที่ใช้เวลานานเพื่อทำให้มันไม่ตรงกันหรือไม่?" ไม่เลย การโทรกลับไม่ได้ทำให้แบบอะซิงโครนัสมันจะยังคงต้องเรียกใช้รหัสที่รันนานก่อนที่จะโทรกลับอย่างชัดเจน


3

สิ่งอื่น ๆ ที่เป็นเช่นนี้คือผลักการเรียกใช้ฟังก์ชันไปที่ด้านล่างของสแต็กเพื่อป้องกันการโอเวอร์โฟลว์ของสแต็กหากคุณเรียกฟังก์ชันซ้ำ นี่เป็นเอฟเฟกต์ของการwhileวนซ้ำ แต่ปล่อยให้เอ็นจิ้น JavaScript เริ่มการจับเวลาแบบอะซิงโครนัสอื่น ๆ


downvote push the function invocation to the bottom of the stackสำหรับเรื่องนี้ สิ่งที่stackคุณกำลังพูดคุยเกี่ยวกับการเป็นที่คลุมเครือ สิ่งที่สำคัญที่สุดคือการsetTimeoutเพิ่ม func นี้จุดสิ้นสุดของวงรอบนี้หรือเริ่มรอบวงถัดไป
สีเขียว

1

โดยการเรียก setTimeout คุณให้เวลาหน้าเพื่อตอบสนองต่อสิ่งที่ผู้ใช้กำลังทำ สิ่งนี้มีประโยชน์อย่างยิ่งสำหรับฟังก์ชันที่เรียกใช้ระหว่างการโหลดหน้า


1

บางกรณีที่ setTimeout มีประโยชน์:

คุณต้องการแบ่งลูปหรือการคำนวณที่ใช้เวลานานเป็นส่วนประกอบที่เล็กลงเพื่อที่เบราว์เซอร์จะไม่ปรากฏเป็น "หยุด" หรือพูดว่า "สคริปต์ในหน้าไม่ว่าง"

คุณต้องการปิดการใช้งานปุ่มส่งแบบฟอร์มเมื่อคลิก แต่ถ้าคุณปิดการใช้งานปุ่มในการจัดการ onClick แบบฟอร์มจะไม่ถูกส่ง setTimeout โดยใช้เวลาเป็นศูนย์ทำการหลอกลวงให้เหตุการณ์จบแบบฟอร์มเพื่อเริ่มส่งจากนั้นปุ่มของคุณสามารถปิดใช้งานได้


2
การปิดใช้งานจะทำได้ดีกว่าในเหตุการณ์ onsubmit มันจะเร็วกว่าและรับประกันว่าจะถูกเรียกก่อนที่จะส่งแบบฟอร์มทางเทคนิคเนื่องจากคุณสามารถหยุดการส่งได้
กริช

จริงแท้แน่นอน. ฉันคิดว่าการปิดใช้งาน onclick นั้นง่ายกว่าสำหรับการสร้างต้นแบบเพราะคุณสามารถพิมพ์ onclick = "this.disabled = true" ในปุ่มในขณะที่การปิดใช้งานการส่งต้องใช้งานมากกว่าเล็กน้อย
fabspro

1

คำตอบเกี่ยวกับการประมวลผลลูปและการแสดงผล DOM ก่อนที่โค้ดอื่นจะเสร็จสมบูรณ์ถูกต้อง ศูนย์หมดเวลาที่สองใน JavaScript ช่วยให้โค้ดหลอกหลายเธรดแม้ว่ามันจะไม่ใช่

ฉันต้องการเพิ่มว่าค่าที่ดีที่สุดสำหรับ cross browser / cross platform zero-second timeout ใน JavaScript เป็นจริงประมาณ 20 มิลลิวินาทีแทน 0 (ศูนย์) เนื่องจากเบราว์เซอร์มือถือจำนวนมากไม่สามารถลงทะเบียน timeouts เล็กกว่า 20 มิลลิวินาทีเนื่องจากข้อ จำกัด นาฬิกา บนชิป AMD

นอกจากนี้กระบวนการที่ใช้เวลายาวนานซึ่งไม่เกี่ยวข้องกับการจัดการ DOM ควรถูกส่งไปยัง Web Workers ในขณะนี้เนื่องจากมีการใช้งาน JavaScript แบบมัลติเธรดที่แท้จริง


2
ฉันสงสัยเล็กน้อยเกี่ยวกับคำตอบของคุณ แต่ได้ถอนมันเพราะมันบังคับให้ฉันทำวิจัยเพิ่มเติมเกี่ยวกับมาตรฐานของเบราว์เซอร์ เมื่อทำการวิจัยมาตรฐานฉันไปที่ที่ฉันไปเสมอ MDN: developer.mozilla.org/en-US/docs/Web/API/window.setTimeout HTML5 spec บอกว่า 4ms ไม่ได้พูดอะไรเกี่ยวกับข้อ จำกัด นาฬิกาบนชิปมือถือ มี googling ที่ลำบากสำหรับแหล่งข้อมูลเพื่อสำรองข้อมูลงบของคุณ Google ค้นหา Dart Language โดย Google ได้ลบ setTimeout ทั้งหมดออกจากวัตถุตัวจับเวลา
John Zabroski

8
(... ) เนื่องจากเบราว์เซอร์มือถือจำนวนมากไม่สามารถลงทะเบียนหมดเวลาน้อยกว่า 20 มิลลิวินาทีเนื่องจากข้อ จำกัด ของนาฬิกา (... )ทุกแพลตฟอร์มมีข้อ จำกัด เวลาเนื่องจากนาฬิกาและไม่มีแพลตฟอร์มใดที่สามารถดำเนินการสิ่งต่อไปได้อย่างแน่นอน ปัจจุบันหนึ่ง การหมดเวลาของ 0ms ขอให้เรียกใช้ฟังก์ชันโดยเร็วที่สุดและข้อ จำกัด ด้านเวลาของแพลตฟอร์มเฉพาะจะไม่เปลี่ยนความหมายของสิ่งนี้ในทางใดทางหนึ่ง
Piotr Dobrogost

1

setTimout เมื่อวันที่ 0 ยังมีประโยชน์มากในรูปแบบของการตั้งค่าสัญญารอการตัดบัญชีซึ่งคุณต้องการกลับทันที:

myObject.prototype.myMethodDeferred = function() {
    var deferredObject = $.Deferred();
    var that = this;  // Because setTimeout won't work right with this
    setTimeout(function() { 
        return myMethodActualWork.call(that, deferredObject);
    }, 0);
    return deferredObject.promise();
}

1

ปัญหาคือคุณพยายามดำเนินการกับ Javascript ในองค์ประกอบที่ไม่มีอยู่ องค์ประกอบยังไม่ได้ถูกโหลดและsetTimeout()ให้เวลามากขึ้นสำหรับองค์ประกอบที่จะโหลดด้วยวิธีต่อไปนี้:

  1. setTimeout()ทำให้เหตุการณ์เป็นแบบอะซิงโครนัสดังนั้นจึงถูกเรียกใช้งานหลังจากรหัสซิงโครนัสทั้งหมดทำให้องค์ประกอบของคุณมีเวลามากขึ้นในการโหลด การโทรกลับแบบอะซิงโครนัสเช่นการโทรกลับsetTimeout()จะถูกวางในคิวเหตุการณ์และวางบนสแต็กโดยวงเหตุการณ์หลังจากสแต็กของรหัสซิงโครนัสว่างเปล่า
  2. ค่า 0 สำหรับ ms เป็นอาร์กิวเมนต์ที่สองในฟังก์ชันsetTimeout()มักจะสูงกว่าเล็กน้อย (4-10ms ขึ้นอยู่กับเบราว์เซอร์) เวลาที่สูงกว่านี้เล็กน้อยที่จำเป็นสำหรับการดำเนินการsetTimeout()เรียกกลับเกิดจากจำนวนของ 'เห็บ' (ที่เห็บถูกผลักดันโทรกลับบนสแต็กถ้ากองว่างเปล่า) ของเหตุการณ์ห่วง เนื่องจากประสิทธิภาพและอายุการใช้งานของแบตเตอรี่ทำให้จำนวนของเห็บในวงเหตุการณ์ถูก จำกัด ไว้ที่จำนวนที่น้อยกว่า 1,000 ครั้งต่อวินาที

-1

Javascript เป็นแอพพลิเคชั่นแบบเธรดเดียวดังนั้นจึงไม่อนุญาตให้เรียกใช้ฟังก์ชันพร้อมกันเพื่อให้เกิดการใช้ลูปเหตุการณ์นี้ ดังนั้นสิ่งที่ setTimeout (fn, 0) ทำเช่นนั้น pussed ลงในภารกิจที่จะดำเนินการเมื่อโทรสแต็คของคุณว่างเปล่า ฉันรู้ว่าคำอธิบายนี้ค่อนข้างน่าเบื่อดังนั้นฉันขอแนะนำให้คุณดูวิดีโอนี้ซึ่งจะช่วยคุณในการทำงานของสิ่งต่าง ๆ ภายใต้ประทุนในเบราว์เซอร์ ลองดูวิดีโอนี้: - https://www.youtube.com/watch?time_continue=392&v=8aGhZQkoFbQ

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