[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()
있습니까?
답변
이 질문에는 다음과 같은 경쟁 조건 이 존재했습니다 .
- 브라우저가 드롭 다운 목록을 초기화하려고 시도하고 선택한 색인을 업데이트 할 준비가되었습니다.
- 선택한 색인을 설정하는 코드
귀하의 코드는 지속적으로이 경쟁에서 승리하고 브라우저가 준비되기 전에 드롭 다운 선택을 설정하려고 시도했습니다. 즉, 버그가 나타납니다.
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 ()”함수를 호출합니다.
-
매우 긴 계산을 수행합니다 (예 : 3 분 소요)
-
계산 결과를 결과 div에 인쇄합니다.
이제 사용자가 테스트를 시작하고 “뭔가 수행”버튼을 클릭하면 페이지가 3 분 동안 아무 것도 보이지 않고 앉아 있습니다.
문제는 명백합니다. “Status”DIV가 필요합니다. 진행 상황을 보여줍니다. 그것이 어떻게 작동하는지 봅시다.
따라서 “Status”DIV (처음 비어 있음)를 추가하고 onclick
핸들러 (function LongCalc()
)를 수정하여 4 가지 작업을 수행하십시오.
-
“계산 중 … ~ 3 분이 걸릴 수 있음”상태를 DIV 상태로 채 웁니다.
-
매우 긴 계산을 수행합니다 (예 : 3 분 소요)
-
계산 결과를 결과 div에 인쇄합니다.
-
“계산 완료”상태를 상태 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
)으로 수정하십시오.
-
“계산 중 … ~ 3 분이 걸릴 수 있음”상태를 DIV 상태로 채 웁니다.
-
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 코드 : ( 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);
});
답변
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)
언급 할 가치가 충분히 있습니다.