setTimeout은 프론트엔드 엔지니어들이 반드시 다룰 함수입니다. 매우 단순하고 평범해 보이는 이름입니다. 타이머라는 이름은 제가 어렸을 때 순진하게도 미래를 제어할 수 있다고 생각했습니다. 단순함을 모른다는 것은 충격적인 비밀을 암시합니다. 이 기능을 처음 사용했을 때 순진하게도 JS에서 멀티스레딩을 구현하기 위한 도구라고 생각했습니다. 탱크 전투를 하며 정말 재미있게 플레이했습니다. 하지만 최전선을 따라 점점 더 나아가면서 그에 대한 이해가 바뀌기 시작했고, 종종 베일에 싸여 있는 것 같았습니다. 알아낼 수 없는 이상한 행동들. 마침내 인내심이 바닥나서 그 마스크를 벗겨내고 알아내겠다고 결심했습니다.
setTimeout의 유래에 대해 이야기하려면 먼저 이것의 공식적인 정의부터 시작해야 합니다. w3c가 정의하는 방법
setTimeout() 메서드는 함수 또는 계산된 표현식이 호출된 후의 밀리초 수를 지정하는 데 사용됩니다.
이런 설명을 보면 타이머이고 우리가 설정한 기능은 시간이 다 되면 실행되는 "알람시계"라는 것을 알 수 있습니다. 하지만 똑똑하다면 어쩔 수 없습니다. 그런데 이런 질문이 있습니다. settimeout이면 (fn,0)은 어떻게 되나요? 정의에 따르면 바로 실행되나요? 실제 테스트의 유일한 기준은 다음 실험을 살펴보겠습니다
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title></title> </head> <body> <script> alert(1); setTimeout("alert(2)", 0); alert(3); </script> </body> </html>
이는 매우 간단한 실험입니다. settimeout(0)이 즉시 실행되면 여기서의 실행 결과는 1->2>3이어야 하지만 실제 결과는 1->3->2입니다. settimeout(0)은 즉시 실행되지 않습니다. 동시에 settimeout의 동작에 대해 매우 이상함을 느낍니다.
js 엔진은 단일 스레드에서 실행됩니다.
먼저 위의 문제를 살펴보겠습니다.
우리는 js 언어 디자인에서 매우 중요한 점은 js 엔진의 실행이 단일 스레드가 아니라는 점을 발견했습니다. 이 기능은 오랫동안 나를 괴롭혔습니다. js는 단일 스레드이기 때문에 누가 타이밍을 잡을지 알 수 없습니다. 누가 ajax 요청을 보내는가? 즉, js입니다. 우리는 브라우저에서 코드를 실행하는 데 익숙하지만 브라우저 자체는 무시합니다. js 엔진은 단일 스레드이지만 브라우저는 다중 스레드가 가능하며 js 엔진은 브라우저의 스레드일 뿐입니다. 타이머 타이밍, 네트워크 요청, 브라우저 렌더링 등은 모두 다른 스레드에 의해 완료됩니다. 이는 사실이 아니지만 여전히 예제
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title></title> </head> <body> </body> <script> var isEnd = true; window.setTimeout(function () { isEnd = false;//1s后,改变isEnd的值 }, 1000); while (isEnd); alert('end'); </script> </html>
isEnd는 기본적으로 true이며 최종 경고는 무한 루프입니다. 타이머를 추가하고 1초 후에 isEnd를 false로 변경했습니다. js 엔진이 스레드이면 1초 후에 경고가 실행됩니다. 그러나 실제 상황은 페이지가 영원히 반복된다는 것입니다. 이는 settimeout이 멀티 스레드로 사용될 수 없다는 좋은 증거입니다. js 엔진 실행은 단일 스레드입니다.
event loop
위의 실험에서 우리는 더욱 혼란스러워졌습니다. settimeout이 정확히 하는 일인가요?
그 답은 여전히 js 언어의 디자인에서 찾아야 한다는 사실이 밝혀졌습니다.
js 엔진은 단일 스레드에서 실행됩니다. 이벤트 중심 언어를 기반으로 합니다. 실행 순서는 이벤트 큐라는 메커니즘을 따릅니다. 그림에서 브라우저에는 이벤트 트리거 서버, 네트워크 요청, 타이머 등과 같은 다양한 스레드가 있음을 알 수 있습니다. 스레드 간의 연결은 이벤트를 기반으로 합니다. 다른 스레드와 관련된 코드를 처리하면 다른 스레드에 배포됩니다. 처리된 후 js 엔진은 대기열에 작업을 추가해야 합니다. 다른 스레드가 실행을 완료하도록 하고, 다른 스레드가 실행을 완료한 후에 js 엔진에 관련 작업을 수행하도록 알리는 이벤트 작업을 추가합니다. 이것이 js의 비동기 프로그래밍 모델입니다.
그래서 settimeout(0)을 되돌아보면, 여기서 js 코드가 실행되면 타이머 스레드가 시작되고 다음 코드가 계속 실행됩니다. 스레드는 지정된 시간 이후에 이벤트 큐에 작업을 삽입하는 것을 볼 수 있습니다. settimeout(0)의 작업은 모든 기본 스레드 작업 뒤에 배치됩니다. 이는 첫 번째 실험 결과가 1->3-2인 이유도 설명합니다.
이는 settimeout의 공식 정의가 혼란스럽다는 것을 보여줍니다.
지정된 시간 내에 작업을 이벤트 대기열에 넣고 실행되기 전에 js 엔진이 유휴 상태가 될 때까지 기다립니다.
js 엔진과 GUI 엔진은 상호 배타적입니다. 이에 대해 말하자면, 브라우저의 또 다른 엔진인 GUI 렌더링 엔진에 대해 이야기해야 합니다. 예를 들어 dom 작업을 위한 코드는 이벤트 대기열에 작업을 생성하고 js는 이 작업은 렌더링을 위해 GUI 엔진을 호출할 때까지 실행됩니다.
js语言设定js引擎与GUI引擎是互斥的,也就是说GUI引擎在渲染时会阻塞js引擎计算.原因很简单,如果在GUI渲染的时候,js改变了dom,那么就会造成渲染不同步. 我们需要深刻理解js引擎与GUI引擎的关系,因为这与我们平时开发息息相关,我们时长会遇到一些很奇葩的渲染问题.看这个例子
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title></title> </head> <body> <table border=1> <tr><td><button id='do'>Do long calc - bad status!</button></td> <td><p id='status'>Not Calculating yet.</p></td> </tr> <tr><td><button id='do_ok'>Do long calc - good status!</button></td> <td><p id='status_ok'>Not Calculating yet.</p></td> </tr> </table> <script> function long_running(status_p) { var result = 0; 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; } } } document.querySelector(status_p).innerHTML = 'calclation done' ; } document.querySelector('#do').onclick = function () { document.querySelector('#status').innerHTML = 'calculating....'; long_running('#status'); }; document.querySelector('#do_ok').onclick = function () { document.querySelector('#status_ok').innerHTML = 'calculating....'; window.setTimeout(function (){ long_running('#status_ok') }, 0); }; </script> </body> </html>
我们希望能看到计算的每一个过程,我们在程序开始,计算,结束时,都执行了一个dom操作,插入了代表当前状态的字符串,Not Calculating yet.和calculating....和calclation done.计算中是一个耗时的3重for循环. 在没有使用settimeout的时候,执行结果是由Not Calculating yet 直接跳到了calclation done.这显然不是我们希望的.而造成这样结果的原因正是js的事件循环单线程机制.dom操作是异步的,for循环计算是同步的.异步操作都会被延迟到同步计算之后执行.也就是代码的执行顺序变了.calculating....和calclation done的dom操作都被放到事件队列后面而且紧跟在一起,造成了丢帧.无法实时的反应.这个例子也告诉了我们,在需要实时反馈的操作,如渲染等,和其他相关同步的代码,要么一起同步,要么一起异步才能保证代码的执行顺序.在js中,就只能让同步代码也异步.即给for计算加上settimeo0t.
settimeout(0)的作用
不同浏览器的实现情况不同,HTML5定义的最小时间间隔是4毫秒. 使用settimeout(0)会使用浏览器支持的最小时间间隔.所以当我们需要把一些操作放到下一帧处理的时候,我们通常使用settimeout(0)来hack.
requestAnimationFrame
这个函数与settimeout很相似,但它是专门为动画而生的.settimeout经常被用来做动画.我们知道动画达到60帧,用户就无法感知画面间隔.每一帧大约16毫秒.而requestAnimationFrame的帧率刚好是这个频率.除此之外相比于settimeout,还有以下的一些优点:
requestAnimationFrame 会把每一帧中的所有DOM操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率,一般来说,这个频率为每秒60帧,每帧大约16毫秒.
在隐藏或不可见的元素中,requestAnimationFrame将不会进行重绘或回流,这当然就意味着更少的的cpu,gpu和内存使用量。
但它优于setTimeout/setInterval的地方在于它是由浏览器专门为动画提供的API,在运行时浏览器会自动优化方法的调用,并且如果页面不是激活状态下的话,动画会自动暂停,有效节省了CPU开销。
总结:
浏览器的内核是多线程的,它们在内核制控下相互配合以保持同步,一个浏览器至少实现三个常驻线程:javascript引擎线程,GUI渲染线程,浏览器事件触发线程。
javascript引擎是基于事件驱动单线程执行的.JS引擎一直等待着任务队列中任务的到来,然后加以处理,浏览器无论什么时候都只有一个JS线程在运行JS程序。
当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行。但需要注意 GUI渲染线程与JS引擎是互斥的,当JS引擎执行时GUI线程会被挂起,GUI更新会被保存在一个队列中等到JS引擎空闲时立即被执行。
当一个事件被触发时该线程会把事件添加到待处理队列的队尾,等待JS引擎的处理。这些事件可来自JavaScript引擎当前执行的代码块如setTimeOut、也可来自浏览器内核的其他线程如鼠标点击、AJAX异步请求等,但由于JS的单线程关系所有这些事件都得排队等待JS引擎处理。
推荐教程:《JS教程》
위 내용은 Node.js setTimeOut() 애플리케이션의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!