SetTimeout (fn, 0) bazen neden yararlıdır?


872

Son zamanlarda kod <select>JavaScript üzerinden dinamik olarak yüklendiği oldukça kötü bir hataya girdim . Bu dinamik olarak yüklenen <select>, önceden seçilmiş bir değere sahipti. IE6, zaten seçilmiş düzeltmek için kodu vardı <option>bazen çünkü <select>'ın selectedIndexdeğeri seçilen paralel olmayabilir olacağını <option>s' indexaşağıda özniteliği:

field.selectedIndex = element.index;

Ancak, bu kod çalışma değildi. Alan selectedIndexdoğru ayarlanmış olsa da , yanlış dizin seçilecektir. Ancak, bir alert()ifadeyi doğru zamanda takarsam, doğru seçenek seçilir. Bunun bir tür zamanlama sorunu olabileceğini düşünerek, daha önce kodda gördüğüm rastgele bir şey denedim:

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

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

Ve bu işe yaradı!

Sorunum için bir çözümüm var, ama bunun neden sorunumu düzelttiğini tam olarak bilmediğim için huzursuzum. Resmi bir açıklaması olan var mı? İşlevimi "daha sonra" özelliğini kullanarak arayarak hangi tarayıcı sorununu önlüyorum setTimeout()?


2
Sorunun çoğu neden faydalı olduğunu açıklıyor. Bunun neden olduğunu bilmeniz gerekiyorsa - cevabımı okuyun: stackoverflow.com/a/23747597/1090562
Salvador Dali

18
Philip Roberts bunu "Olay döngüsü ne halt?" youtube.com/watch?v=8aGhZQkoFbQ
vasa

Eğer aceleniz varsa, bu videonun sorusunu tam olarak ele almaya başlar: youtu.be/8aGhZQkoFbQ?t=14m54s . Regarldless, tüm video kesinlikle izlemeye değer.
William Hampshire

4
setTimeout(fn)setTimeout(fn, 0)btw ile aynıdır .
vsync

Yanıtlar:


827

Soruda, aşağıdakiler arasında bir yarış durumu vardı :

  1. Tarayıcının, seçili dizini güncellenmeye hazır açılır listeyi başlatma girişimi ve
  2. Seçilen dizini ayarlamak için kodunuz

Kodunuz sürekli olarak bu yarışı kazanıyordu ve tarayıcı hazır olmadan önce açılır seçimi ayarlamaya çalışıyor, bu da hatanın görüneceği anlamına geliyor.

Bu yarış, JavaScript'in sayfa oluşturmayla paylaşılan tek bir yürütme iş parçacığına sahip olması nedeniyle vardı . Aslında, JavaScript çalıştırmak DOM'un güncellenmesini engeller.

Geçici çözümünüz:

setTimeout(callback, 0)

setTimeoutBir geri çağırma ile çağırma ve ikinci bağımsız değişken olarak sıfır, geri çağrıyı sekme odaklandığında ve JavaScript yürütme iş parçacığı meşgul olmadığında, mümkün olan en kısa gecikmeden sonra eşzamansız olarak çalışacak şekilde zamanlar.

Bu nedenle OP'nin çözümü, seçilen endeksin ayarlanması yaklaşık 10 ms gecikmekti. Bu, tarayıcıya DOM'u başlatma ve hatayı düzeltme fırsatı verdi.

Internet Explorer'ın her sürümü ilginç davranışlar sergiledi ve bu tür bir geçici çözüm bazen gerekliydi. Alternatif olarak, OP'nin kod tabanında gerçek bir hata olabilir.


Philip Roberts'ın "Olay döngüsü ne halt?" daha ayrıntılı bir açıklama için.


276
'Çözüm, oluşturma iş parçacıklarının yakalanmasına izin vermek için JavaScript yürütmesini "duraklatmak". Tamamen doğru değil, setTimeout'un yaptığı şey, tarayıcı olay kuyruğuna yeni bir olay eklemek ve oluşturma motoru zaten bu kuyrukta (tamamen doğru değil, yeterince yakın), bu nedenle setTimeout olayından önce yürütülür.
David Mulder

48
Evet, bu çok daha ayrıntılı ve çok daha doğru bir cevap. Ama benimki hilenin neden işe yaradığını anlaması için "yeterince doğru".
staticsan

2
@DavidMulder, tarayıcı ayrıştırması css ve javascript yürütme iş parçacığından farklı bir iş parçacığı oluşturma anlamına gelir mi?
Jason

8
Hayır, prensipte aynı iş parçacığında ayrıştırılırlar, aksi takdirde birkaç DOM manipülasyon satırı, yürütme hızı üzerinde son derece kötü bir etkiye sahip olacak şekilde her zaman yeniden akışları tetikler.
David Mulder

30
Bu video neden ayarladığımızın
davibq

665

Önsöz:

Diğer cevapların bazıları doğrudur, ancak çözülen sorunun ne olduğunu gerçekten göstermiyorum, bu yüzden bu cevabı detaylı çizimi sunmak için oluşturdum.

Bu nedenle, tarayıcının ne yaptığını ve kullanımının nasıl setTimeout()yardımcı olduğunu ayrıntılı olarak gözden geçiriyorum . Uzunca görünüyor ama aslında çok basit ve anlaşılır - sadece çok ayrıntılı yaptım.

GÜNCELLEME: Aşağıdaki açıklamayı canlı olarak göstermek için bir JSFiddle yaptım: http://jsfiddle.net/C2YBE/31/ . @ThangChung'a kickstart'a yardım ettiği için çok teşekkürler .

GÜNCELLEME2: JSFiddle web sitesinin ölmesi veya kodu silmesi durumunda, kodu en sonunda bu cevaba ekledim.


DETAYLAR :

"Bir şey yap" düğmesi ve sonuç div içeren bir web uygulaması düşünün.

onClick"Bir şey yapmak" düğmesi için işleyici 2 şey yapar "LongCalc ()", bir işlevini çağırır:

  1. Çok uzun bir hesaplama yapar (diyelim 3 dakika sürer)

  2. Hesaplama sonuçlarını sonuç div'a yazdırır.

Şimdi, kullanıcılarınız bunu test etmeye başlıyor, "bir şeyler yap" düğmesini tıklıyor ve sayfa 3 dakika boyunca hiçbir şey yapmıyor gibi görünüyor, huzursuz oluyorlar, düğmeyi tekrar tıklayın, 1 dakika bekleyin, hiçbir şey olmuyor, düğmeyi tekrar tıklayın ...

Sorun açıktır - neler olduğunu gösteren bir "Durum" DIV'si istiyorsunuz. Bunun nasıl çalıştığını görelim.


Böylece bir "Durum" DIV'si (başlangıçta boş) ekler ve onclickişleyiciyi (işlevi LongCalc()) 4 şey yapacak şekilde değiştirirsiniz :

  1. "Hesaplanıyor ... ~ 3 dakika sürebilir" durumunu DIV durumuna çevirin

  2. Çok uzun bir hesaplama yapar (diyelim 3 dakika sürer)

  3. Hesaplama sonuçlarını sonuç div'a yazdırır.

  4. "Hesaplama tamamlandı" durumunu DIV durumuna çevirin

Ve, uygulamayı tekrar test etmek için kullanıcılara mutlu bir şekilde verirsiniz.

Çok kızgın görünerek size geri dönüyorlar. Ve düğmeyi tıkladıklarında, Durum DIV'nin "Hesaplanıyor ..." durumu ile hiç güncellenmediğini açıklayın !!!


Başınızı kaşıyorsunuz, StackOverflow'da (veya dokümanlar veya google'ı okuyorsunuz) soruyorsunuz ve sorunu fark ediyorsunuz:

Tarayıcı, olaylardan kaynaklanan tüm "TODO" görevlerini (UI görevleri ve JavaScript komutları) tek bir sıraya yerleştirir . Ve ne yazık ki, "Durum" DIV yeni "Hesaplanıyor ..." değeri ile yeniden çizmek, sıranın sonuna giden ayrı bir TODO!

Kullanıcının testi sırasındaki olayların dökümü, her olaydan sonra kuyruğun içeriği:

  • kuyruk: [Empty]
  • Olay: düğmesine tıklayın. Etkinlikten sonraki kuyruk:[Execute OnClick handler(lines 1-4)]
  • Olay: OnClick işleyicisinde ilk satırı yürütün (örn. Durum DIV değerini değiştirin). Olaydan sonra Sıraya: [Execute OnClick handler(lines 2-4), re-draw Status DIV with new "Calculating" value]. DOM değişiklikleri anında gerçekleşirken, karşılık gelen DOM öğesini yeniden çizmek için, DOM değişikliği tarafından tetiklenen ve kuyruğun sonunda giden yeni bir etkinliğe ihtiyacınız olduğunu lütfen unutmayın .
  • SORUN!!! SORUN!!! Ayrıntılar aşağıda açıklanmıştır.
  • Olay: İşleyicide ikinci satırı çalıştır (hesaplama). Sonra Sıraya: [Execute OnClick handler(lines 3-4), re-draw Status DIV with "Calculating" value].
  • Olay: İşleyicide 3. satırı yürütün (sonuç DIV'sini doldurun). Sonra Sıraya: [Execute OnClick handler(line 4), re-draw Status DIV with "Calculating" value, re-draw result DIV with result].
  • Olay: İşleyicide 4. satırı yürütün (DIV durumu "DONE" ile doldurun). Kuyruk: [Execute OnClick handler, re-draw Status DIV with "Calculating" value, re-draw result DIV with result; re-draw Status DIV with "DONE" value].
  • Etkinlik: zımni yürütmek returndan onclickişleyici alt. "OnClick işleyicisini yürütün" kuyruğunu kaldırır ve kuyruktaki bir sonraki öğeyi yürütmeye başlarız.
  • NOT: Hesaplamayı zaten bitirdiğimizden, kullanıcı için 3 dakika geçti. Yeniden çekme etkinliği henüz gerçekleşmedi !!!
  • Olay: Durum Hesaplama'yı "Hesaplanıyor" değeriyle yeniden çizin. Yeniden çiziyoruz ve bunu kuyruktan alıyoruz.
  • Olay: Sonuç DIV ile sonuç değerini yeniden çizin. Yeniden çiziyoruz ve bunu kuyruktan alıyoruz.
  • Olay: Durum DIV'sini "Bitti" değeriyle yeniden çizin. Yeniden çiziyoruz ve bunu kuyruktan alıyoruz. Keskin gözlü izleyiciler, mikrosaniyenin bir kısmı için "Hesaplanıyor" değeri yanıp sönen "Durum DIV'sini" bile fark edebilir - HESAPLAMADAN SONRA BİTİRİLDİ

Dolayısıyla, temel sorun, "Durum" DIV için yeniden çekme olayının, kuyrukta 3 dakika süren "yürütme satırı 2" olayından SONRA, sıraya yerleştirilmesidir, bu nedenle gerçek yeniden çekme işlemi Hesaplama yapıldıktan SONRA.


Kurtarmaya geliyor setTimeout(). Nasıl yardımcı olur? Çünkü uzun yürütme kodunu çağırarak setTimeout, aslında 2 olay oluşturursunuz: setTimeoutyürütmenin kendisi ve (0 zaman aşımı nedeniyle), yürütülmekte olan kod için ayrı kuyruk girişi.

Bu nedenle, sorununuzu çözmek için onClickişleyicinizi İKİ ifade olacak şekilde değiştirirsiniz (yeni bir işlevde veya yalnızca bir blokta onClick):

  1. "Hesaplanıyor ... ~ 3 dakika sürebilir" durumunu DIV durumuna çevirin

  2. setTimeout()0 zaman aşımı ve LongCalc()işlev çağrısı ile yürütün .

    LongCalc()işlevi neredeyse son kez aynıdır, ama açıkçası "Hesaplanıyor ..." durumu DIV güncelleme ilk adım olarak; bunun yerine hesaplamayı hemen başlatır.

Peki, olay dizisi ve kuyruk şimdi nasıl görünüyor?

  • kuyruk: [Empty]
  • Olay: düğmesine tıklayın. Etkinlikten sonraki kuyruk:[Execute OnClick handler(status update, setTimeout() call)]
  • Olay: OnClick işleyicisinde ilk satırı yürütün (örn. Durum DIV değerini değiştirin). Olaydan sonra Sıraya: [Execute OnClick handler(which is a setTimeout call), re-draw Status DIV with new "Calculating" value].
  • Olay: İşleyicide ikinci satırı yürütün (setTimeout çağrısı). Sonra Sıraya: [re-draw Status DIV with "Calculating" value]. Kuyrukta 0 saniye daha yeni bir şey yok.
  • Olay: 0 saniye sonra zaman aşımından alarm söner. Sonra Sıraya: [re-draw Status DIV with "Calculating" value, execute LongCalc (lines 1-3)].
  • Olay: Durum Hesaplama'yı "Hesaplanıyor" değeriyle yeniden çizin . Sonra Sıraya: [execute LongCalc (lines 1-3)]. Bu yeniden çekme olayının, alarm çalmadan ÖNCE gerçekleşebileceğini lütfen unutmayın; bu da aynı şekilde çalışır.
  • ...

Yaşasın! Durum DIV, hesaplama başlamadan önce "Hesaplanıyor ..." olarak güncellendi !!!



Aşağıda bu örnekleri gösteren JSFiddle örnek kodu verilmiştir: http://jsfiddle.net/C2YBE/31/ :

HTML Kodu:

<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 kodu: (Üzerinde çalıştırılır onDomReadyve jQuery 1.9 gerekebilir)

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
harika cevap DVK! Örneğinizi
kumikoda 10:13

4
Gerçekten harika bir cevap, DVK. Sadece hayal etmeyi kolaylaştırmak için, bu kodu jsfiddle jsfiddle.net/thangchung/LVAaV
thangchung

4
@ThangChung - JSFiddle'da daha iyi bir sürüm (her durumda bir tane olmak üzere 2 düğme) yapmaya çalıştım. Chrome ve IE'de bir demo olarak çalışır, ancak bir nedenle FF'de çalışmaz - bkz. Jsfiddle.net/C2YBE/31 . Neden
FF'nin

1
@DVK "Tarayıcı, olaylardan kaynaklanan tüm" TODO "görevlerini (UI görevleri ve JavaScript komutları) tek bir sıraya yerleştirir". Efendim, bunun için lütfen kaynak sağlayabilir misiniz? Imho arent tarayıcılar farklı UI (oluşturma motoru) ve JS konuları olması gerekiyordu .... suç amaçlanan .... sadece öğrenmek istiyorum ..
bhavya_w

1
@bhavya_w Hayır, her şey bir iş parçacığında gerçekleşir. Uzun bir js hesaplamasının
UI'yi



23

Çoğu tarayıcının ana iş parçacığı adı verilen ve bazı JavaScript görevlerini, UI güncellemelerini yürütmekten sorumlu olan bir işlemi vardır: boyama, yeniden çizim veya yeniden akış, vb.

Bazı JavaScript yürütme ve kullanıcı arayüzü güncelleme görevleri tarayıcı mesaj kuyruğuna kuyruğa alınır, ardından yürütülecek tarayıcı ana iş parçacığına gönderilir.

Ana iş parçacığı meşgulken kullanıcı arabirimi güncelleştirmeleri oluşturulduğunda, görevler ileti kuyruğuna eklenir.

setTimeout(fn, 0);bunu fnyürütülecek kuyruğun sonuna ekleyin . Belirli bir süre sonra ileti kuyruğuna eklenecek bir görev zamanlar.


"Her JavaScript yürütme ve kullanıcı arayüzü güncelleme görevi tarayıcı olay kuyruğu sistemine eklenir, ardından bu görevler gerçekleştirilecek olan tarayıcı ana kullanıcı arabirimi iş parçacığına gönderilir." .... kaynak lütfen?
bhavya_w

4
Yüksek Performanslı JavaScript (Nicholas Zakas, Stoyan Stefanov, Ross Harmes, Julien Lecomte ve Matt Sweeney)
Arley

Bunun için aşağı oy add this fn to the end of the queue. En önemlisi, tam olarak setTimeoutbu fonk, bu döngü döngüsünün sonuna veya bir sonraki döngü döngüsünün çok başlangıcına eklenir.
Yeşil

19

Burada çelişkili olarak cevaplanan cevaplar var ve kanıt olmadan kime inanacağını bilmenin bir yolu yok. İşte @DVK'nın doğru ve @SalvadorDali'nin yanlış olduğunun kanıtı. İkinci iddia:

"Ve işte nedeni: 0 milisaniyelik bir zaman gecikmesi ile setTimeout'a sahip olmak mümkün değildir. Minimum değer tarayıcı tarafından belirlenir ve 0 milisaniye değildir. Tarihsel olarak tarayıcılar bu minimum değeri 10 milisaniyeye ayarlar, ancak HTML5 özellikleri ve modern tarayıcılar 4 milisaniyede ayarlanmış. "

4 ms'lik minimum zaman aşımı, olanlarla alakasızdır. Gerçekten olan şey, setTimeout'un geri arama işlevini yürütme kuyruğunun sonuna itmesidir. SetTimeout'tan (geri arama, 0) sonra çalıştırılması birkaç saniye süren engelleme kodunuz varsa, engelleme kodu bitinceye kadar geri arama birkaç saniye yürütülmez. Bu kodu deneyin:

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

Çıktı:

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

cevabınız bir şey kanıtlamaz. Makinenizde belirli bir durumda bilgisayarın size bazı rakamlar attığını gösterir. İlgili bir şeyi kanıtlamak için, birkaç kod satırından ve birkaç sayıdan biraz daha fazlasına ihtiyacınız vardır.
Salvador Dali

6
@SalvadorDali, kanıtımın çoğu insanın anlayabileceği kadar açık olduğuna inanıyorum. Sanırım savunmacı hissediyorsun ve bunu anlamak için çaba göstermedin. Açıklığa kavuşturmaktan mutluluk duyarım, ama neyi anlamadığınızı bilmiyorum. Sonuçlarımdan şüpheleniyorsanız kodu kendi makinenizde çalıştırmayı deneyin.
Vladimir Kornea

Son kez açıklığa kavuşturmaya çalışacağım. Cevabım zaman aşımı, aralıktaki bazı ilginç özellikleri açıklığa kavuşturmak ve geçerli ve tanınabilir kaynaklar tarafından iyi destekleniyor. Bunun yanlış olduğunu ve herhangi bir nedenle bir kanıt adı verdiğiniz kod parçanızı işaret ettiğine dair güçlü bir iddiada bulundunuz. Kod parçanızla ilgili yanlış bir şey yok (buna karşı değilim). Sadece cevabımın yanlış olmadığını söylüyorum. Bazı noktaları açıklığa kavuşturuyor. Bu savaşı durduracağım, çünkü bir nokta göremiyorum.
Salvador Dali

15

Bunu yapmanın bir nedeni, kodun yürütülmesini ayrı ve sonraki bir olay döngüsüne ertelemektir. Bir tür tarayıcı olayına yanıt verirken (örneğin fare tıklaması), bazen yalnızca geçerli olay işlendikten sonra işlemleri gerçekleştirmek gerekir . setTimeout()Tesis bunu yapmak için en basit yoldur.

düzenlemek şimdi 2015 olduğunu ben de var olduğunu unutmamalıdır requestAnimationFrame()tam olarak aynı değil ama yakın yeterince var ki, setTimeout(fn, 0)'s bahsetmemiz söyledi.


Burası tam olarak kullanıldığını gördüğüm yerlerden biri. =)
Zaibot


2
Ayrı bir sonraki olay döngü kod yürütülmesine erteleme olduğunu nasıl bir anlayabilirim: müteakip olay döngü ? Mevcut olay döngüsünün ne olduğunu nasıl anlarsınız? Şimdi hangi olay döngüsünde olduğunuzu nasıl anlarsınız?
Yeşil

@Yeşil iyi, gerçekten değil; JavaScript çalışma zamanının ne yaptığına dair doğrudan bir görünürlük yoktur.
Sivri

requestAnimationFrame, IE ve Firefox ile bazen yaşadığım sorunu çözdü
David

9

Bu eski cevapları olan eski bir sorudur. Bu soruna yeni bir bakış eklemek ve bunun neden işe yaradığını ve bunun neden faydalı olduğunu değil cevabını vermek istedim.

Yani iki fonksiyonunuz var:

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

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

ve sonra f1(); f2();sadece ikincisinin önce idam edildiğini görmek için onları aşağıdaki sırayla arayın .

Ve işte nedeni: setTimeout0 milisaniye zaman gecikmesi olması mümkün değildir . Minimum değer, tarayıcı tarafından belirlenir ve 0 milisaniye değil. Tarihsel olarak tarayıcılar bu minimum değeri 10 milisaniyeye ayarlar, ancak HTML5 özellikleri ve modern tarayıcılarda 4 milisaniyeye ayarlanmıştır.

Yuvalama düzeyi 5'ten fazlaysa ve zaman aşımı 4'ten azsa, zaman aşımını 4'e yükseltin.

Ayrıca Mozilla'dan:

Modern bir tarayıcıda 0 ms zaman aşımı uygulamak için, burada açıklandığı gibi window.postMessage () öğesini kullanabilirsiniz .

PS bilgileri aşağıdaki makaleyi okuduktan sonra alınır .


1
@ user2407309 Şaka mı yapıyorsun? HTML5 teknik özelliğinin yanlış olduğunu ve doğru olduğunu mu söylüyorsunuz? Aşağı inmeden önce kaynakları okuyun ve güçlü iddialarda bulunun. Cevabım HTML spesifikasyonu ve geçmiş kayıtlarına dayanıyor. Tamamen aynı şeyleri tekrar tekrar açıklayan cevabı yapmak yerine, yeni bir şey ekledim, önceki cevaplarda gösterilmeyen bir şey. Bunun tek sebep olduğunu söylemiyorum, sadece yeni bir şey gösteriyorum.
Salvador Dali

1
Bu yanlış: "İşte nedeni: 0 milisaniye zaman gecikmesi ile setTimeout'a sahip olmak mümkün değildir." Bu yüzden değil. 4 ms gecikme, neden setTimeout(fn,0)faydalı olduğu ile ilgisizdir .
Vladimir Kornea

@ user2407309 kolayca "başkaları tarafından belirtilen nedenlere eklemek için, mümkün değildir ...." olarak değiştirilebilir. Bu yüzden, özellikle bunun cevabını yeni bir şey söylemiyorsa, bu nedenle aşağıya düşürmek saçma. Sadece küçük bir düzenleme yeterli olacaktır.
Salvador Dali

10
Salvador Dali: Buradaki mikro alev savaşının duygusal yönlerini görmezden gelirseniz, muhtemelen @VladimirKornea'nın haklı olduğunu kabul etmek zorunda kalacaksınız. Tarayıcıların 0 ms gecikmeyi 4 ms'ye eşledikleri doğrudur, ancak olmasalar bile sonuçlar yine aynı olacaktır. Buradaki sürüş mekanizması, kodun çağrı yığını yerine sıraya itilmiş olmasıdır. Bu mükemmel JSConf sunumuna bir göz atın, sorunu açıklığa kavuşturmaya yardımcı olabilir: youtube.com/watch?v=8aGhZQkoFbQ
hashchange

1
En az 4 ms'lik kalifiye bir teklifinizle ilgili teklifinizin neden küresel asgari 4 ms olduğunu düşündüğünüz konusunda kafam karıştı. HTML5 spesifikasyonundaki teklifinizin gösterdiği gibi, minimum çağrı yalnızca beş seviyenin derinliklerine setTimeout/ setIntervaldaha fazlasına iç içe yerleştirdiğinizde 4 ms'dir ; Eğer yapmadıysanız, minimum 0 ms'dir (eksik zaman makinelerinde ne olur). Mozilla'nın dokümanları, sadece iç içe geçmiş vakaları değil, tekrarlananları kapsayacak şekilde genişletir (bu nedenle setInterval0 aralığı ile hemen birkaç kez yeniden zamanlanır, daha sonra bundan daha uzun süre gecikir), ancak setTimeoutminimal iç içe yerleştirmenin basit kullanımlarının hemen kuyruğa girmesine izin verilir.
ShadowRanger


7

Bu iki en yüksek puanlı cevap yanlış. Eşzamanlılık modeli ve olay döngüsündeki MDN açıklamasına göz atın ve neler olduğunu netleştirin (MDN kaynağı gerçek bir mücevherdir). Ve sadece setTimeout bu küçük sorunu "çözmenin" yanı sıra kodunuza beklenmedik sorunlar eklemek olabilir.

Ne var aslında burada devam değil mi "tarayıcı tam olarak hazır olmayabilir henüz eşzamanlılık, çünkü" dayalı falan "her satırın sıranın arkasına eklenen alır bir olaydır".

Jsfiddle Dvk tarafından sağlanan gerçekten bir sorun göstermektedir, ancak bunun için onun açıklaması doğru değil.

Kodunda olan şey click, #dodüğmedeki olaya ilk önce bir olay işleyici ekliyor olmasıdır .

Ardından, düğmeyi gerçekten tıklattığınızda, messageolay işleyici işlevine başvuruda bulunan ve öğesine eklenir message queue. Ne zaman event loopbu mesajı ulaştığında, bir oluşturur framejsfiddle tıklama olay işleyicisi işlev çağrısı ile, yığının üzerine.

Ve işte ilginç olan yer burası. Javascript'i asenkron olarak düşünmeye o kadar alışkınız ki, bu küçük gerçeği göz ardı etmeye meyilliyiz: Herhangi bir kare, bir sonraki kare yürütülmeden önce tam olarak yürütülmelidir . Eşzamanlılık yok, insanlar.

Ne anlama geliyor? Bu, ileti kuyruğundan bir işlev her çağrıldığında, oluşturduğu yığın boşaltılana kadar kuyruğu bloke ettiği anlamına gelir. Veya daha genel terimlerle, işlev dönünceye kadar engeller. Ayrıca , DOM oluşturma işlemleri, kaydırma ve başka şeyler dahil her şeyi engeller . Onaylamak istiyorsanız, kemandaki uzun çalışma işleminin süresini artırmaya çalışın (örn. Dış döngüyü 10 kez daha çalıştırın) ve çalışırken bu sayfayı kaydıramayacağınızı göreceksiniz. Yeterince uzun sürerse, tarayıcınız size işlemi yanıtlamak isteyip istemediğinizi soracaktır çünkü sayfa yanıt vermiyor. Çerçeve yürütülür ve olay döngüsü ve ileti kuyruğu bitene kadar takılı kalır.

Peki neden metnin bu yan etkisi güncellenmiyor? Eğer ederken Çünkü var DOM öğesinin değerini değişti - yapabilirsiniz console.log()değeri hemen değiştirdikten sonra ve görüyoruz etmiştir (ki gösteriler Dvk yaptığı açıklama doğru değil neden) değiştirilmiştir - Tarayıcı tüketme yığını bekliyor ( ongeri dönmek için işleyici işlevi) ve böylece bitecek mesaj, böylece sonunda çalışma zamanı tarafından mutasyon operasyonumuza bir tepki olarak eklenen mesajı yürütmeye ve kullanıcı arayüzündeki bu mutasyonu yansıtmaya çalışabilir. .

Çünkü kodun çalışmasını bitirmesini bekliyoruz. Biz genellikle "birileri bunu getirin ve daha sonra sonuçlarla bu işlevi çağırır, teşekkürler, ve ben şimdi imma dönüş yapmak, şimdi ne olursa olsun yapmak" demedik, genellikle olay tabanlı asenkron Javascript ile yaptığımız gibi. Bir tıklama olay işleyicisi işlevi giriyoruz, bir DOM öğesini güncelliyoruz, başka bir işlev çağırıyoruz, diğer işlev uzun süre çalışıyor ve sonra geri dönüyor, daha sonra aynı DOM öğesini güncelliyoruz ve sonra ilk işlevden geri dönüyoruz, etkili bir şekilde boşalıyoruz yığını. Ve sonra tarayıcı sıradaki bir sonraki mesaja ulaşabilir, bu da dahili bir "DOM-mutasyon" tipi olayı tetikleyerek bizim tarafımızdan oluşturulan bir mesaj olabilir.

Tarayıcı kullanıcı arayüzü, şu anda yürütülen çerçeve tamamlanıncaya (işlev geri döndü) kadar kullanıcı arayüzünü güncelleyemez (veya etmemeyi seçer). Şahsen, bunun kısıtlamadan ziyade tasarımdan kaynaklandığını düşünüyorum.

O zaman neden setTimeoutişe yarıyor? Bunu yapar, çünkü uzun süren işlev çağrısını kendi çerçevesinden etkili bir şekilde kaldırır, daha sonra windowbağlamda yürütülecek şekilde zamanlar , böylece kendisi hemen geri dönebilir ve ileti sırasının diğer iletileri işlemesine izin verebilir. Ve fikir şu ki, DOM'daki metni değiştirirken bizim tarafımızdan Javascript'te tetiklenen UI "güncelleme" mesajı artık uzun süren işlev için sıralanan mesajın önünde, böylece UI güncellemesi engellenmeden önce gerçekleşiyor uzun zamandır.

A) Uzun süren işlevin çalıştığında hala her şeyi engellediğini ve b) UI güncellemesinin mesaj kuyruğunda aslında bunun ötesinde olduğunu garanti etmezsiniz. Haziran 2018 Chrome tarayıcımda, değeri 0kemanın gösterdiği sorunu "düzeltmiyor" - 10. Aslında bununla biraz boğuldum, çünkü UI güncelleme mesajının kendisinden önce sıraya alınması gerektiği mantıklı görünüyor, çünkü uzun süren işlevi "daha sonra" çalışacak şekilde zamanlamadan önce tetikleyicisi yürütülüyor. Ama belki de V8 motorunda müdahale edebilecek bazı optimizasyonlar var, ya da benim anlayışım eksik.

Tamam, peki, kullanımdaki sorun setTimeoutnedir ve bu özel durum için daha iyi bir çözüm nedir?

Öncelikle, setTimeoutbaşka bir sorunu hafifletmek için böyle bir olay işleyicide kullanma sorunu, diğer kodlarla uğraşmaya eğilimlidir. İşte işimden gerçek bir örnek:

Bir meslektaş, olay döngüsü hakkında yanlış bilgilendirilmiş bir anlayışla, bazı şablon oluşturma kodunun oluşturulması setTimeout 0için Javascript'i "işlemeye" çalıştı . Artık sormak için burada değil, ama belki de oluşturma hızını ölçmek için zamanlayıcılar eklediğini varsayabilirim (ki bu fonksiyonların geri dönüşü olacaktır) ve bu yaklaşımı kullanmanın bu fonksiyondan şaşırtıcı derecede hızlı yanıtlar vereceğini buldu.

İlk sorun açıktır; javascript işleyemezsiniz, bu yüzden gizleme eklerken burada hiçbir şey kazanmazsınız. İkincisi, artık bir şablonun oluşturulmasını, çok şablonun oluşturulmasını bekleyebilecek olası olay dinleyicileri yığınından etkili bir şekilde ayırdınız, ancak çok iyi olmasa da. Bu işlevin gerçek davranışı artık - bilmeden öyle - onu çalıştıracak ya da ona bağlı olacak herhangi bir işlev olduğu için deterministik değildi. Eğitimli tahminler yapabilirsiniz, ancak davranışını düzgün bir şekilde kodlayamazsınız.

Onun mantığına bağlı yeni bir olay işleyicisi yazarken "düzeltme" de kullanmaktı setTimeout 0. Ancak, bu bir düzeltme değildir, anlaşılması zordur ve böyle bir koddan kaynaklanan hataların hatalarını ayıklamak eğlenceli değildir. Bazen hiçbir sorun yoktur, bazen de sürekli olarak başarısız olur ve daha sonra, platformun mevcut performansına ve o sırada neler olup bittiğine bağlı olarak, bazen aralıklı olarak çalışır ve kırılır. Bu yüzden , ne yaptığınızı ve sonuçların ne olduğunu gerçekten bilmiyorsanız, bu hack'i (kişisel olarak bir hack'tir ve hepimiz bilmeliyiz) kullanmaya karşı kişisel olarak tavsiye ederim.

Ama bunun yerine ne yapabiliriz ? Başvurulan MDN makalesinde belirtildiği gibi, işi birden çok mesaja (eğer mümkünse) ayırın, böylece sıraya alınan diğer iletiler çalışmanızla araya eklenebilir ve çalışırken yürütülebilir veya çalışabilecek bir web çalışanı kullanabilir sayfanıza paralel olarak ve hesaplamalarla işiniz bittiğinde sonuçları döndürür.

Oh, ve "Peki, asenkron işlevini yapmak için uzun süredir devam eden fonksiyona bir geri arama koyamadım mı?" Diye düşünüyorsan o zaman hayır. Geri arama, eşzamansız hale getirmez, geri aramanızı açıkça çağırmadan önce uzun süren kodu çalıştırması gerekir.


3

Bunun yaptığı bir diğer şey, işlev çağrışımını yığının altına doğru iterek, bir işlevi özyinelemeli olarak çağırırsanız yığının taşmasını engellemektir. Bunun bir whiledöngü etkisi vardır, ancak JavaScript motorunun diğer eşzamansız zamanlayıcıları tetiklemesine izin verir.


Bunun için aşağı oy push the function invocation to the bottom of the stack. Ne stackdiyorsun sen belirsiz. En önemlisi, tam olarak setTimeoutbu fonk, bu döngü döngüsünün sonuna veya bir sonraki döngü döngüsünün çok başlangıcına eklenir.
Yeşil

1

SetTimeout'u çağırarak sayfaya kullanıcının yaptığı her şeye tepki vermesi için zaman tanırsınız. Bu özellikle sayfa yükleme sırasında çalıştırılan işlevler için yararlıdır.


1

SetTimeout'un yararlı olduğu diğer bazı durumlar:

Tarayıcının 'donuyor' gibi görünmemesi veya "Sayfadaki komut dosyası meşgul" dememesi için uzun süren bir döngüyü veya hesaplamayı daha küçük bileşenlere bölmek istiyorsunuz.

Tıklandığında form gönderme düğmesini devre dışı bırakmak istiyorsunuz, ancak onClick işleyicisindeki düğmeyi devre dışı bırakırsanız form gönderilmez. sıfır ile setTimeout hile yaparak etkinliğin sona ermesine, formun gönderilmeye başlamasına izin verir, ardından düğmeniz devre dışı bırakılabilir.


2
Devre dışı bırakma, yayınlama olayında daha iyi yapılır; daha hızlı olur ve form teknik olarak gönderilmeden önce çağrılması garanti edilir, çünkü gönderimi durdurabilirsiniz.
Kris

Çok doğru. Onclick devre dışı bırakma prototipleme için daha kolay olduğunu varsayalım çünkü sadece onclick = "this.disabled = true" yazabilirsiniz, oysa gönderme devre dışı bırakmak biraz daha fazla çalışma gerektirir.
fabspro

1

Yürütme döngüleri ve diğer kod tamamlanmadan önce DOM'u oluşturma ile ilgili yanıtlar doğrudur. JavaScript'teki sıfır saniyelik zaman aşımı, kodun olmasa bile sözde çok iş parçacıklı olmasına yardımcı olur.

JavaScript'te çapraz tarayıcı / çapraz platform sıfır saniyelik zaman aşımı için EN İYİ değerinin aslında 0 (sıfır) yerine yaklaşık 20 milisaniye olduğunu eklemek istiyorum, çünkü birçok mobil tarayıcı saat sınırlamaları nedeniyle 20 milisaniyeden daha küçük zaman aşımlarını kaydedemiyor AMD yongalarında.

Ayrıca, DOM manipülasyonu içermeyen uzun süren işlemler, JavaScript'in gerçek çok iş parçacıklı yürütülmesini sağladıkları için şimdi Web Çalışanlarına gönderilmelidir.


2
Cevabınız hakkında biraz şüpheliyim, ancak iptal ettim çünkü tarayıcı standartları hakkında ek araştırma yapmaya zorladım. Standartları araştırırken MDN: developer.mozilla.org/en-US/docs/Web/API/window.setTimeout HTML5 spec 4ms diyor. Mobil yongalardaki saat sınırlamaları hakkında hiçbir şey söylemez. İfadelerinizi yedeklemek için bir bilgi kaynağı için googling yapmak zor. Google tarafından Dart Dili öğrendim SetTimeout tamamen bir Timer nesnesi lehine kaldırıldı.
John Zabroski

8
(...) birçok mobil tarayıcı saat sınırlamaları nedeniyle 20 milisaniyeden daha kısa zaman aşımlarını kaydedemediği için (...) Her platformun saati nedeniyle zamanlama sınırlamaları vardır ve hiçbir platform bir sonraki şeyi tam olarak 0 ms'den sonra çalıştıramaz. mevcut olan. 0 ms zaman aşımı bir işlevin mümkün olan en kısa sürede yürütülmesini ister ve belirli platformun zamanlama sınırlamaları bunun anlamını hiçbir şekilde değiştirmez.
Piotr Dobrogost

1

setTimout on 0, hemen geri dönmek istediğiniz ertelenmiş bir söz verme düzeninde de çok yararlıdır:

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

Sorun, var olmayan bir öğe üzerinde bir Javascript işlemi gerçekleştirmeye çalışıyordunuz. Öğe henüz yüklenmemişti ve setTimeout()bir ögenin yüklenmesi için daha fazla zaman veriyor:

  1. setTimeout()etkinliğin eşzamansız olmasına neden olur, bu nedenle öğenizin yüklenmesine daha fazla zaman tanıyarak tüm eşzamanlı koddan sonra yürütülür. Geri arama gibi eşzamansız geri aramalar setTimeout(), olay kuyruğuna yerleştirilir ve olay döngüsü tarafından yığına konur senkron kod yığını boş olduktan sonra yığına yerleştirilir.
  2. setTimeout()İşlevdeki ikinci bir bağımsız değişken olarak ms için 0 değeri genellikle biraz daha yüksektir (tarayıcıya bağlı olarak 4-10 ms). setTimeout()Geri aramaların yürütülmesi için gereken bu biraz daha yüksek süreye , olay döngüsünün 'keneler' miktarı (burada bir işaret, yığın boşsa yığına geri arama gönderir) neden olur. Performans ve pil ömrü nedeniyle, olay döngüsündeki kenelerin miktarı saniyede 1000 kereden az bir miktarla sınırlıdır .

-1

Javascript tek iş parçacıklı bir uygulama böylece fonksiyonu aynı anda çalıştırmak için izin vermez böylece bu olay döngüler elde etmek için kullanın. Yani tam olarak ne setTimeout (fn, 0), çağrı yığını boş olduğunda yürütülen görev arayışına pussed. Bu açıklamanın oldukça sıkıcı olduğunu biliyorum, bu yüzden bu videoyu izlemenizi tavsiye ederim, bu, tarayıcıda başlıkların altında nasıl çalıştığını size yardımcı olacaktır. Bu videoyu izleyin: - https://www.youtube.com/watch?time_continue=392&v=8aGhZQkoFbQ

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.