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

최근 코드가 <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()있습니까?



답변

이 질문에는 다음과 같은 경쟁 조건 이 존재했습니다 .

  1. 브라우저가 드롭 다운 목록을 초기화하려고 시도하고 선택한 색인을 업데이트 할 준비가되었습니다.
  2. 선택한 색인을 설정하는 코드

귀하의 코드는 지속적으로이 경쟁에서 승리하고 브라우저가 준비되기 전에 드롭 다운 선택을 설정하려고 시도했습니다. 즉, 버그가 나타납니다.

JavaScript에는 페이지 렌더링과 공유 되는 단일 실행 스레드 가 있기 때문에 이러한 경쟁이 존재했습니다 . 실제로 JavaScript를 실행하면 DOM 업데이트가 차단됩니다.

해결 방법은 다음과 같습니다.

setTimeout(callback, 0)

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

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

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


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


답변

머리말:

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

따라서, 나는 게시하고 자세한 워크를 통해 브라우저가 수행하는 작업을하고 사용하는 방법을 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);
});


답변

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


답변

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

이것을 확인하십시오 : setTimeout


답변

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

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

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

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


답변

여기에는 상충되는 답이 있으며, 증거가 없으면 누구를 믿어야할지 알 수 없습니다. @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


답변

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

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