왜 setTimeout (fn, 0)이 유용한가?


872

최근 코드가 <select>JavaScript를 통해 동적으로 로드되는 다소 불쾌한 버그가 발생했습니다 . 이 동적으로로드 된 <select>값은 미리 선택된 값입니다. IE6에서, 우리는 이미 선택을 수정하는 코드를 가지고 <option>때때로 때문에, <select>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
살바도르 달리

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콜백으로 호출 하고 두 번째 인수로 0을 설정하면 가능한 가장 짧은 지연 후 콜백이 비동기 적 으로 실행되도록 예약합니다 . 탭에 초점이 있고 JavaScript 실행 스레드가 사용 중이 아닐 때 약 10ms가 걸립니다.

따라서 OP의 솔루션은 선택한 인덱스의 설정 인 약 10ms 지연되는 것이 었습니다. 이로 인해 브라우저는 DOM을 초기화하고 버그를 수정했습니다.

모든 버전의 Internet Explorer는 기발한 동작을 보였으며 이러한 해결 방법은 때때로 필요했습니다. 또는 OP 코드베이스의 진정한 버그 일 수 있습니다.


필립 로버츠의 "이벤트 루프 란 무엇입니까?" 더 철저한 설명.


276
'해결책은 렌더링 스레드를 따라 잡기 위해 JavaScript 실행을 "일시 중지"하는 것입니다.' setTimeout이 수행하는 것은 브라우저 이벤트 큐에 새 이벤트를 추가하고 렌더링 엔진이 이미 해당 큐에 있기 때문에 (완전히 사실은 아니지만 충분히 가깝습니다) setTimeout 이벤트 전에 실행됩니다.
David Mulder

48
그렇습니다. 훨씬 상세하고 정답입니다. 그러나 사람들이 트릭이 작동하는 이유를 이해할 수 있도록 "충분히 정확"합니다.
staticsan

2
@DavidMulder, 브라우저가 CSS를 구문 분석하고 자바 스크립트 실행 스레드와 다른 스레드에서 렌더링을 의미합니까?
Jason

8
아니, 원칙적으로 동일한 스레드에서 파싱됩니다. 그렇지 않으면 몇 줄의 DOM 조작이 항상 리플 로우를 트리거하여 실행 속도에 매우 나쁜 영향을 미칩니다.
David Mulder

30
이 동영상은 우리의 setTimeout 이유에 대한 가장 최선의 방법으로 설명하기 0 2014.jsconf.eu/speakers/...
davibq

665

머리말:

다른 답변 중 일부는 정확하지만 실제로 해결되는 문제가 무엇인지 설명하지는 않으므로 자세한 답변을 제시하기 위해이 답변을 작성했습니다.

따라서, 나는 게시하고 자세한 워크를 통해 브라우저가 수행하는 작업을하고 사용하는 방법을 setTimeout()하는 데 도움이 . 길어 보이지만 실제로는 매우 간단하고 간단합니다. 방금 상세하게 만들었습니다.

업데이트 : http://jsfiddle.net/C2YBE/31/ 아래에 설명을 실연하기 위해 JSFiddle을 만들었습니다 . 많은 감사 를 킥 스타트 돕는 @ThangChung합니다.

업데이트 2 : JSFiddle 웹 사이트가 죽거나 코드를 삭제하는 경우 마지막 에이 답변에 코드를 추가했습니다.


세부 사항 :

"뭔가"버튼과 결과 div가있는 웹 앱을 상상해보십시오.

onClick"일부"버튼 의 핸들러는 "LongCalc ()"함수를 호출합니다.

  1. 매우 긴 계산을 수행합니다 (예 : 3 분 소요)

  2. 계산 결과를 결과 div에 인쇄합니다.

이제 사용자가 테스트를 시작하고 "뭔가 수행"버튼을 클릭하면 페이지가 3 분 동안 아무 것도 보이지 않고 앉아 있습니다.

문제는 명백합니다. "Status"DIV가 필요합니다. 진행 상황을 보여줍니다. 그것이 어떻게 작동하는지 봅시다.


따라서 "Status"DIV (처음 비어 있음)를 추가하고 onclick핸들러 (function LongCalc())를 수정하여 4 가지 작업을 수행하십시오.

  1. "계산 중 ... ~ 3 분이 걸릴 수 있음"상태를 DIV 상태로 채 웁니다.

  2. 매우 긴 계산을 수행합니다 (예 : 3 분 소요)

  3. 계산 결과를 결과 div에 인쇄합니다.

  4. "계산 완료"상태를 상태 DIV로 채 웁니다.

또한, 사용자가 행복하게 앱을 다시 테스트하도록합니다.

그들은 매우 화난 당신에게 돌아옵니다. 버튼을 클릭했을 때 Status DIV가 "Calculating ..."상태로 업데이트되지 않았다고 설명합니다 !!!


머리를 긁고 StackOverflow에 대해 문의하거나 문서 또는 Google을 읽고 문제를 인식하십시오.

브라우저는 이벤트에서 발생하는 모든 "TODO"태스크 (UI 태스크 및 JavaScript 명령 모두)를 단일 큐에 배치 합니다. 불행히도 새로운 "계산 중 ..."값으로 "상태"DIV를 다시 그리면 큐의 끝으로가는 별도의 TODO입니다!

다음은 사용자 테스트 중 발생한 이벤트와 각 이벤트 후 큐의 내용입니다.

  • 열: [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].
  • 이벤트 : 처리기에서 세 번째 줄을 실행하십시오 (결과 DIV를 채우십시오). 이후 대기열 : [Execute OnClick handler(line 4), re-draw Status DIV with "Calculating" value, re-draw result DIV with result].
  • 이벤트 : 처리기에서 4 번째 줄을 실행합니다 ( "DONE"으로 상태 DIV를 채 웁니다). 대기열 : [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 핸들러"를 꺼내고 큐에서 다음 항목을 실행하기 시작합니다.
  • 참고 : 이미 계산을 마쳤으므로 3 분이 이미 지나갔습니다. 다시 그리기 이벤트가 아직 발생하지 않았습니다 !!!
  • 이벤트 : "계산 중"값으로 상태 DIV를 다시 그립니다. 우리는 다시 뽑아서 대기열에서 가져옵니다.
  • 이벤트 : 결과 값으로 결과 DIV를 다시 그립니다. 우리는 다시 뽑아서 대기열에서 가져옵니다.
  • 이벤트 : 상태 DIV를 "완료"값으로 다시 그립니다. 우리는 다시 뽑아서 대기열에서 가져옵니다. 뾰족한 시청자들은 심지어 " 마무리가 끝난 후 마이크로 초의 몇 분 동안"계산 "값이 깜박이는 상태 DIV를 볼 수 있습니다.

따라서 근본적인 문제는 "상태"DIV에 대한 다시 그리기 이벤트가 3 분이 걸리는 "행 2 실행"이벤트가 끝난 후 큐에 배치되어 실제 다시 그리기가 수행 될 때까지 발생하지 않는다는 것입니다. 계산이 완료된 후


구조에 온다 setTimeout(). 어떻게 도움이 되나요? 를 통해 장기 실행 코드를 호출 setTimeout하면 실제로는 setTimeout실행 자체와 실행중인 코드에 대한 별도의 큐 항목 (0 개의 시간 초과로 인해)이라는 두 개의 이벤트가 실제로 생성 됩니다.

따라서 문제를 해결하려면 onClick처리기를 두 개의 문 (새 함수 또는 블록 내의 블록 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)]. 이 다시 그리기 이벤트는 실제로 알람이 울리기 전에 실제로 발생할 수 있습니다.
  • ...

만세! 계산이 시작되기 전에 상태 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 코드 : ( onDomReadyJQuery 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 dec

4
@ThangChung-JSFiddle에서 더 나은 버전 (각 버튼마다 하나씩 2 개의 버튼)을 만들려고했습니다. Chrome 및 IE에서는 데모로 작동하지만 어떤 이유로 FF에서는 데모가 아닙니다 . jsfiddle.net/C2YBE/31을 참조하십시오 . FF가 왜 작동하지 않는지 물었습니다 : stackoverflow.com/questions/20747591/…
DVK

1
@DVK "브라우저는 이벤트로 인해 발생하는 모든"TODO "태스크 (UI 태스크 및 JavaScript 명령 모두)를 단일 큐에 배치합니다." 각하의 소스를 제공해 주시겠습니까? 이럴 .... 어떤 범죄 의도하지 .... 그냥 배우고 싶은 다른 UI (렌더링 엔진)과 JS 스레드를 했어야 브라우저를이 arent ..
bhavya_w

1
@bhavya_w 아니오, 모든 것이 하나의 스레드에서 발생합니다. 이것은 긴 JS 계산은 UI 차단할 수있는 이유
세바 Kerckhof

88

JavaScript 타이머 작동 방식 에 대한 John Resig의 기사를 살펴보십시오 . 시간 초과를 설정하면 엔진이 현재 호출 스택을 실행할 때까지 실제로 비동기 코드를 대기열에 넣습니다.


23

setTimeout() DOM 요소가 0으로 설정되어 있어도 DOM 요소가로드 될 때까지 시간을 투자합니다.

이것을 확인하십시오 : setTimeout


23

대부분의 브라우저에는 메인 스레드라는 프로세스가 있으며, 이는 일부 JavaScript 작업, UI 업데이트 (예 : 페인팅, 다시 그리기 또는 리플 로우 등)를 실행하는 프로세스입니다.

일부 JavaScript 실행 및 UI 업데이트 작업은 브라우저 메시지 큐에 대기 한 다음 실행되도록 브라우저 기본 스레드로 발송됩니다.

기본 스레드가 사용 중일 때 UI 업데이트가 생성되면 작업이 메시지 대기열에 추가됩니다.

setTimeout(fn, 0);fn실행할 큐의 끝에 이것을 추가하십시오 . 주어진 시간이 지나면 메시지 대기열에 작업이 추가되도록 예약합니다.


"모든 JavaScript 실행 및 UI 업데이트 작업이 브라우저 이벤트 큐 시스템에 추가되면 해당 작업이 수행 될 브라우저 기본 UI 스레드로 발송됩니다.".... source please?
bhavya_w

4
고성능 JavaScript (Nicholas Zakas, Stoyan Stefanov, Ross Harmes, Julien Lecomte 및 Matt Sweeney)
Arley

이것에 대한 공감대 add this fn to the end of the queue. 가장 중요한 것은 setTimeout이 기능, 루프주기의 끝 또는 다음 루프주기의 시작을 정확하게 추가 하는 것 입니다.
그린

19

여기에는 상충되는 답이 있으며, 증거가 없으면 누구를 믿어야할지 알 수 없습니다. @DVK가 옳고 @SalvadorDali가 잘못되었다는 증거가 있습니다. 후자는 주장한다 :

"그리고 여기에 이유가 있습니다. 시간 지연이 0 밀리 초인 setTimeout을 사용할 수 없습니다. 최소값은 브라우저에 의해 결정되며 0 밀리 초가 아닙니다. 역사적으로 브라우저는이 최소값을 10 밀리 초로 설정하지만 HTML5 사양 및 최신 브라우저는 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

당신의 대답은 아무것도 증명하지 못합니다. 컴퓨터의 특정 상황에서 컴퓨터가 몇 가지 숫자를 던지는 것을 보여줍니다. 관련된 것을 증명하려면 몇 줄의 코드와 몇 개 이상의 숫자가 필요합니다.
살바도르 달리 1

6
@SalvadorDali, 나는 내 증거가 대부분의 사람들이 이해하기에 충분히 분명하다고 생각합니다. 나는 당신이 방어 적이라고 생각하고 그것을 이해하려고 노력하지 않았다고 생각합니다. 나는 그것을 명확히하려고 노력할 것이지만, 당신이 이해하지 못하는 것이 무엇인지 모르겠습니다. 내 결과가 의심되는 경우 자신의 컴퓨터에서 코드를 실행하십시오.
Vladimir Kornea

나는 마지막으로 그것을 분명히하려고 노력할 것입니다. 내 대답은 시간 초과, 간격에서 흥미로운 속성 중 일부를 명확하게 설명하며 유효하고 인식 가능한 소스에서 잘 지원됩니다. 당신은 그것이 틀렸다고 강력하게 주장했고 어떤 이유로 당신이 증거를 명명 한 코드를 가리 켰습니다. 코드 조각에 아무런 문제가 없습니다 (반대하지 않습니다). 나는 단지 내 대답이 잘못되었다고 말하고 있습니다. 그것은 몇 가지 요점을 명확하게한다. 요점을 볼 수 없기 때문에이 전쟁을 중단 할 것입니다.
살바도르 달리

15

그렇게하는 한 가지 이유는 코드 실행을 별도의 후속 이벤트 루프로 연기하는 것입니다. 어떤 종류의 브라우저 이벤트 (예 : 마우스 클릭)에 응답 할 때 때로는 현재 이벤트가 처리 된 후에 만 작업을 수행해야 합니다. 그만큼setTimeout()시설을 할 수있는 간단한 방법입니다.

2015 년이므로 이제 편집 해야합니다. 또한 requestAnimationFrame()정확히 동일하지는 않지만 setTimeout(fn, 0)언급 할 가치가 충분히 있습니다.


이것은 내가 그것을 본 곳 중 하나입니다. =)
Zaibot

참고 :이 답변은에서 여기에 합병되었다 stackoverflow.com/questions/4574940/...
Shog9

2
별도의 후속 이벤트 루프에 코드의 실행을 연기하는 방법은 알아 않습니다 이후 이벤트 루프를 ? 현재 이벤트 루프가 무엇인지 어떻게 알 수 있습니까? 현재 어떤 이벤트 루프 턴에서 어떻게 알 수 있습니까?
Green

@ 그린 글쎄, 당신은 정말로; JavaScript 런타임이 무엇인지에 대한 직접적인 가시성은 없습니다.
Pointy

requestAnimationFrame 내가 IE와 파이어 폭스는 때때로 UI를 업데이트하지으로 가지고 있던 문제 해결
데이비드

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로 늘리십시오.

또한 모질라에서 :

최신 브라우저에서 0ms 시간 초과를 구현하려면 여기에 설명 된대로 window.postMessage ()를 사용할 수 있습니다 .

PS 정보는 다음 기사를 읽은 후에 가져옵니다 .


1
농담하니? HTML5 사양이 잘못되었고 정확하다는 의미입니까? 공감하기 전에 출처를 읽고 강력한 주장을하십시오. 내 대답은 HTML 사양과 기록을 기반으로합니다. 완전히 동일한 내용을 반복해서 설명하는 대답을하는 대신 이전 답변에 표시되지 않은 새로운 것을 추가했습니다. 나는 이것이 유일한 이유라고 말하지 않고 단지 새로운 것을 보여주고 있습니다.
살바도르 달리

1
"그리고 그 이유는 다음과 같습니다. 시간 지연이 0 밀리 초인 setTimeout을 사용할 수 없습니다." 그렇습니다. 4ms 지연은 왜 setTimeout(fn,0)유용한 지와 관련이 없습니다 .
Vladimir Kornea

@ user2407309는 "다른 사람들이 언급 한 이유를 추가하기 위해 불가능합니다 ...."로 쉽게 수정할 수 있습니다. 따라서 자신의 답변이 새로운 것을 말하지 않는다면 특히 이것 때문에 공감하는 것은 어리 석습니다. 작은 편집만으로 충분합니다.
살바도르 달리

10
살바도르 달리 : 여기에서 화염 전쟁의 감정적 측면을 무시한다면, @VladimirKornea가 옳다는 것을 인정해야 할 것입니다. 브라우저가 0ms 지연을 4ms로 매핑하는 것은 사실이지만, 그렇지 않은 경우에도 결과는 동일합니다. 여기서의 구동 메커니즘은 코드가 호출 스택이 아닌 대기열로 푸시된다는 것입니다. 이 훌륭한 JSConf 프리젠 테이션을 살펴보면 youtube.com/watch?v=8aGhZQkoFbQ
hashchange

1
자격이 된 최소 4ms에 대한 견적이 글로벌 최소 4ms라고 생각하는 이유가 혼란 스럽습니다. HTML5의 스펙 쇼에서 견적으로, 최소는 중첩 호출했습니다에만 4 밀리 setTimeout/ setInterval깊은 개 이상 수준; 그렇지 않은 경우 최소값은 0ms입니다 (타임머신 부족). Mozilla의 문서는 중첩 된 사례뿐만 아니라 반복되는 범위를 포함하도록 확장됩니다 (따라서 setInterval간격이 0 인 경우 몇 번 즉시 일정을 조정 한 다음 더 길게 지연시킵니다). setTimeout최소한의 중첩으로 간단한 사용 으로 즉시 대기열에 넣을 수 있습니다.
ShadowRanger

8

지속 시간이 전달 0되었으므로 setTimeout실행 흐름에서 전달 된 코드를 제거하기위한 것 같습니다 . 따라서 시간이 걸릴 수있는 함수 인 경우 후속 코드가 실행되지 않습니다.



7

이 두 가지 최상위 답변 모두 잘못되었습니다. 동시성 모델과 이벤트 루프에 대한 MDN 설명을 확인하고 무슨 일이 일어나고 있는지 명확하게 보여야합니다 (MDN 리소스는 실제 gem 임). 그리고 간단히 setTimeout 하면 코드에 예기치 않은 문제가 추가 될뿐 아니라이 작은 문제를 "해결"할 수 있습니다.

무엇 실제로 여기에가는 것이 아니다 "브라우저가 아직 준비되지 않을 수도 있습니다 아직 동시성 때문"에 따라 또는 뭔가 "각 라인은 큐의 뒤쪽에 추가됩니다 이벤트입니다".

jsfiddle DVK에 의해 제공되는 참으로 문제를 보여,하지만 그의 설명은 올바르지 않습니다.

코드에서 일어나고있는 일은 먼저 이벤트 처리기를 버튼 의 click이벤트에 연결한다는 것 #do입니다.

그런 다음 실제로 버튼을 클릭 message하면 이벤트 핸들러 함수를 참조하여가 생성되어에 추가됩니다 message queue. (가) 때 event loop이 메시지가 도달, 그것은을 생성 framejsfiddle의 클릭 이벤트 핸들러에 함수 호출 스택에.

그리고 이것이 흥미로운 곳입니다. 우리는 자바 스크립트를 비동기식으로 생각하는 데 익숙해 져서이 작은 사실을 간과하기 쉽습니다 . 다음 프레임을 실행하기 전에 모든 프레임을 완전히 실행해야 합니다. 동시성은 없습니다.

이것은 무엇을 의미 하는가? 메시지 큐에서 함수가 호출 될 때마다 생성 된 스택이 비워 질 때까지 큐를 차단합니다. 또는보다 일반적인 용어로, 함수가 반환 될 때까지 차단됩니다. 그리고 그것은 모든 것을 차단합니다 DOM 렌더링 작업, 스크롤 및 기타를 포함하여 합니다. 확인을 원하면 바이올린에서 장기 실행 작업의 지속 시간을 늘리십시오 (예 : 외부 루프를 10 회 더 실행). 실행 중에는 페이지를 스크롤 할 수 없습니다. 오래 실행되면 브라우저가 페이지를 응답하지 않기 때문에 프로세스 종료 여부를 묻습니다. 프레임이 실행되고 있으며 이벤트 루프와 메시지 큐가 완료 될 때까지 고정됩니다.

그렇다면 왜 텍스트의 부작용이 업데이트되지 않습니까? 당신이 동안 때문에 DOM에있는 요소의 값을 변경 - 당신이 할 수있는 console.log()그 가치 즉시 변경 한 후하고 있음을 볼 수 있다 (이 쇼 DVK의 설명이 정확하지 않은 이유를) 변경 - 브라우저가 고갈에 스택을 기다리고 있습니다 ( on핸들러 함수 반환) 따라서 메시지가 끝나기 때문에 런타임에 돌연변이 작업에 대한 반응으로 추가 된 메시지를 실행하고 UI에 해당 돌연변이를 반영하기 위해 결국 실행할 수 있습니다. .

실제로 코드 실행이 완료되기를 기다리고 있기 때문입니다. 우리는 보통 이벤트 기반의 비동기 자바 스크립트와 마찬가지로 "누군가 이것을 가져 와서 결과와 함께이 함수를 호출한다. 고마워, 이제 imma return, 이제 무엇이든한다"고 말하지 않았다. click 이벤트 핸들러 함수를 입력하고, DOM 요소를 업데이트하고, 다른 함수를 호출하고, 다른 함수가 오랫동안 작동 한 다음 반환됩니다. 같은 DOM 요소를 업데이트 한 다음 초기 함수에서 반환하여 효과적으로 비 웁니다. 스택. 그런 다음 브라우저는 큐의 다음 메시지로 이동할 수 있습니다. 내부 "DOM- 돌연변이"유형 이벤트를 트리거하여 생성 한 메시지 일 수 있습니다.

브라우저 UI는 현재 실행중인 프레임이 완료 될 때까지 (기능이 반환 될 때까지) UI를 업데이트 할 수 없습니다. 개인적으로 저는 이것이 제한보다는 설계에 의한 것이라고 생각합니다.

setTimeout그러면 왜 작동합니까? 그렇게하면 자체 프레임에서 장기 실행 함수에 대한 호출을 효과적으로 제거하고 window컨텍스트 에서 나중에 실행되도록 예약 하여 자체적 으로 즉시 리턴 하고 메시지 큐가 다른 메시지를 처리 ​​할 수 ​​있도록합니다. 그리고 아이디어는 DOM에서 텍스트를 변경할 때 Javascript로 우리에 의해 트리거 된 UI "업데이트"메시지가 장기 실행 기능을 위해 대기중인 메시지보다 앞서서 UI 업데이트가 차단되기 전에 발생한다는 것입니다 오랫동안.

a) 장기 실행 기능 실행될 때 모든 것을 여전히 차단합니다. b) UI 업데이트가 실제로 메시지 큐에서 우선한다는 보장은 없습니다. 2018 년 6 월 Chrome 브라우저에서 값이 0바이올린이 보여주는 문제를 "수정"하지는 않습니다. 실제로는 이것으로 조금 어지럽습니다. 장기 실행 함수가 "나중에"실행되도록 예약하기 전에 트리거가 실행되기 때문에 UI 업데이트 메시지를 큐에 대기시켜야한다는 것이 논리적으로 보입니다. 그러나 V8 엔진에 방해가 될 수있는 최적화가 있거나 아마도 내 이해가 부족할 수 있습니다.

자,을 사용할 때의 문제점은 setTimeout무엇이며,이 특별한 경우에 더 나은 해결책은 무엇입니까?

우선, setTimeout다른 문제를 완화하기 위해 이와 같은 이벤트 핸들러에서 사용 하는 문제는 다른 코드와 혼동되기 쉽습니다. 내 작품의 실제 예는 다음과 같습니다.

이벤트 루프에 대해 잘못 이해 한 동료는 템플릿 렌더링 코드를 렌더링에 사용 setTimeout 0하여 Javascript를 "스레딩"하려고했습니다 . 그는 더 이상 여기에 묻지 않지만 렌더링 속도를 측정하기 위해 타이머를 삽입했다고 가정 할 수 있습니다 (기능의 즉각적인 반환).이 접근 방식을 사용하면 해당 기능에서 빠른 응답을 할 수 있음을 알았습니다.

첫 번째 문제는 명백하다. 자바 스크립트를 스레드 할 수 없으므로 난독 화를 추가하는 동안 아무것도 얻지 못합니다. 두 번째로, 템플릿 렌더링이 예상되지 않았을 가능성이있는 가능한 이벤트 리스너 스택에서 템플릿 렌더링을 효과적으로 분리했습니다. 이 함수의 실제 동작은 이제 무의식적으로 함수를 실행하거나 함수에 의존하는 함수와 마찬가지로 결정적이지 않았습니다. 교육받은 추측을 할 수는 있지만 그 행동을 제대로 코딩 할 수는 없습니다.

논리에 의존하는 새로운 이벤트 핸들러를 작성할 때 "수정" 사용했습니다 setTimeout 0. 그러나 이것은 수정이 아니며 이해하기 어렵고 이와 같은 코드로 인한 오류를 디버깅하는 것은 재미가 없습니다. 때로는 아무런 문제가 없으며 다른 경우에는 일관되게 실패하고 다시 플랫폼의 현재 성능과 당시 진행되는 다른 일에 따라 때때로 작동하고 중단되는 경우가 있습니다. 나는 개인적으로이 해킹을 (그 사용에 대해 조언을 할 이유입니다 입니다 당신이 정말 당신이 무슨 일을하는지 알고 결과가 무엇인가하지 않으면, 해킹, 우리는 모두 그것이 알고 있어야합니다).

그러나 우리는 대신 무엇을 할 수 있습니까? 참조 된 MDN 기사에서 알 수 있듯이 작업이 여러 메시지로 분할되면 (가능한 경우) 대기중인 다른 메시지가 작업과 인터리브되어 실행 중에 실행될 수 있으며, 웹 워커를 사용할 수 있습니다. 페이지와 함께 계산이 완료되면 결과를 반환합니다.

오, 그리고 만약 당신이 생각한다면, "음, 비 동기화하기 위해 장기 실행 함수에 콜백을 넣을 수 없었습니까?" 콜백은 비동기식으로 만들지 않으므로 명시 적으로 콜백을 호출하기 전에 장기 실행 코드를 실행해야합니다.


3

다른 방법은 함수 호출을 스택의 맨 아래로 밀어서 재귀 적으로 함수를 호출하는 경우 스택 오버플로를 방지하는 것입니다. 이것은 while루프 효과가 있지만 JavaScript 엔진이 다른 비동기 타이머를 시작하게합니다.


이것에 대한 공감 push the function invocation to the bottom of the stack. 당신 stack이 말하는 것은 모호합니다. 가장 중요한 것은 setTimeout이 기능, 루프주기의 끝 또는 다음 루프주기의 시작을 정확하게 추가 하는 것 입니다.
그린

1

setTimeout을 호출하면 사용자가 수행하는 작업에 반응 할 페이지 시간이 제공됩니다. 페이지로드 중에 실행되는 기능에 특히 유용합니다.


1

setTimeout이 유용한 다른 경우들 :

브라우저가 '정지'하거나 "페이지의 스크립트가 사용 중"으로 표시되지 않도록 장기 실행 루프 또는 계산을 더 작은 구성 요소로 나누려고합니다.

클릭하면 양식 제출 버튼을 사용하지 않으려 고하지만 onClick 핸들러에서 버튼을 사용하지 않으면 양식이 제출되지 않습니다. 시간이 0 인 setTimeout은 트릭을 수행하여 이벤트가 종료되고 양식이 제출되기 시작하면 단추를 비활성화 할 수 있습니다.


2
onsubmit 이벤트에서 비활성화하는 것이 더 좋습니다. 제출을 중지 할 수 있기 때문에 양식이 기술적으로 제출되기 전에 더 빠르며 호출되는 것이 보장됩니다.
Kris

매우 사실입니다. 버튼을 클릭하면 onclick = "this.disabled = true"를 입력 할 수 있지만 제출시 비활성화하려면 약간 더 많은 작업이 필요하기 때문에 onclick 비활성화가 프로토 타입 제작에 더 쉽다고 생각합니다.
fabspro

1

다른 코드가 완성되기 전에 실행 루프 및 DOM 렌더링에 대한 답변이 정확합니다. JavaScript의 0 초 타임 아웃은 코드가 멀티 스레드되지 않도록하는데 도움이됩니다.

많은 모바일 브라우저가 클럭 제한으로 인해 20 밀리 초보다 작은 타임 아웃을 등록 할 수 없기 때문에 JavaScript에서 크로스 브라우저 / 크로스 플랫폼 0 초 타임 아웃에 대한 BEST 값이 실제로 0이 아니라 약 20 밀리 초라고 덧붙이고 싶습니다. AMD 칩에서.

또한 DOM 조작을 포함하지 않는 장기 실행 프로세스는 이제 JavaScript의 다중 스레드 실행을 제공하므로 웹 작업자에게 전송해야합니다.


2
귀하의 답변에 대해 약간 회의적이지만 브라우저 표준에 대한 추가 연구를 강요했기 때문에 반대 의견을 표명했습니다. 표준을 연구 할 때 항상가는 곳으로갑니다. MDN : developer.mozilla.org/en-US/docs/Web/API/window.setTimeout HTML5 사양은 4ms입니다. 모바일 칩의 클럭 제한에 대해서는 언급하지 않습니다. 정보 소스가 성명을 뒷받침하기 위해 인터넷 검색에 어려움을 겪고 있습니다. Google의 Dart Language가 Timer 객체를 선호하여 setTimeout을 모두 제거한다는 것을 알았습니다.
존 자브로 스키

8
(...) 많은 모바일 브라우저 인해 클럭 제한 20 밀리 초 (...)보다 작은 시간 제한 등록 할 수 없기 때문에 인해 시계로 모든 플랫폼은 타이밍 제한이를 어떠한 플랫폼은 다음 실행할 수없는 일을 정확하게 후 에선 0ms 현재 하나. 0ms의 시간 초과는 가능한 빨리 기능을 실행하도록 요청하며 특정 플랫폼의 타이밍 제한으로 인해이 의미가 변경되지 않습니다.
Piotr Dobrogost

1

setTimout on 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()이벤트가되도록 ansynchronous 그러므로 당신의 요소를 부하에 더 많은 시간을주고, 모든 동기 코드 후에 실행된다. 콜백과 같은 비동기 콜백 setTimeout()이벤트 큐에 배치되고 이벤트 루프에 의해 스택에 배치됩니다. 동기 코드 스택이 비어있는 후 .
  2. 함수의 두 번째 인수 인 ms의 값 0 setTimeout()은 종종 약간 높습니다 (브라우저에 따라 4-10ms). setTimeout()콜백 을 실행하는 데 필요한 약간 더 높은 시간 은 이벤트 루프의 '틱'(스택이 비어있는 경우 틱이 스택에서 콜백을 푸시하는)의 양으로 인해 발생합니다. 성능 및 배터리 수명으로 인해 이벤트 루프의 틱 양은 초당 1000 회 미만 의 특정 양으로 제한됩니다 .

-1

Javascript는 단일 스레드 응용 프로그램이므로 기능을 동시에 실행 하여이 이벤트 루프를 사용하도록 허용하지 않습니다. 따라서 setTimeout (fn, 0)은 호출 스택이 비어있을 때 실행되는 작업 퀘스트로 소멸됩니다. 이 설명이 지루하다는 것을 알고 있으므로이 비디오를 살펴 보는 것이 좋습니다.이 방법은 브라우저에서 작동하는 방식에 도움이됩니다. 이 비디오를 확인하십시오 : -https : //www.youtube.com/watch ? time_continue=392&v=8aGhZQkoFbQ

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.