이 글은 주로 JavaScript 타이밍 메커니즘에 대한 심층적인 이해를 소개하며, 관심 있는 친구들이 참고할 수 있습니다.
이 글에서는 JavaScript 타이밍 메커니즘을 소개합니다. JavaScript의 타이밍 메커니즘을 이해하려면 JavaScript의 실행 메커니즘을 알아야 합니다.
우선 JavaScript는 단일 스레드(JavaScript 엔진 스레드) 이벤트 중심이라고 명시되어 있습니다.
1. 브라우저에는 여러 개의 스레드가 있습니다.
브라우저에 포함된 가장 기본적인 스레드:
1. JavaScript 엔진 스레드입니다.
2. 타이머 스레드, setInterval 및 setTimeout이 이 스레드를 트리거합니다.
3. 브라우저 이벤트 트리거 스레드, 이 스레드는 onclick, onmousemove 및 기타 브라우저 이벤트를 트리거합니다.
4. 인터페이스 렌더링 스레드는 브라우저 인터페이스의 HTML 요소를 렌더링하는 역할을 합니다. 참고: JavaScript 엔진이 스크립트를 실행하는 동안 인터페이스 렌더링 스레드는 일시 중지된 상태입니다. 즉, JavaScript를 사용하여 인터페이스의 노드를 작동하는 경우에는 JavaScript 엔진 스레드가 유휴 상태가 될 때까지 즉시 반영되지 않습니다. (마지막으로 말씀드리겠습니다)
5. HTTP 요청 스레드(Ajax 요청도 그중에 있습니다).
위 스레드는 서로 협력하여 브라우저 커널의 제어 하에 작업을 완료합니다(자세한 내용은 모르겠습니다).
2. 작업 대기열
우리는 JavaScript가 단일 스레드이고 모든 JavaScript 코드가 JavaScript 엔진 스레드에서 실행된다는 것을 알고 있습니다. Ruan Yifeng 선생님의 글에서는 이 스레드를 메인 스레드라고 부르며 실행 스택입니다. (다음 내용은 주로 Ruan Yifeng 선생님의 글에 대한 이해와 요약을 바탕으로 작성되었습니다.)
이 JavaScript 코드를 하나씩 작업으로 간주할 수 있습니다. 이러한 작업은 동기 작업과 비동기 작업으로 구분됩니다. 동기 작업(변수 할당 문, 경고 문, 함수 선언문 등)은 메인 스레드에서 직접 순서대로 실행되고, 비동기 작업(예: 브라우저 이벤트 트리거 스레드에 의해 트리거되는 다양한 이벤트)은 반환된 서버 응답을 사용합니다. Ajax 등)은 작업 큐(이벤트 큐, 메시지 큐라고도 함)에 시간순으로 대기하며 실행을 기다리고 있습니다. 메인 스레드의 작업이 실행되는 동안 대기열에 대기 중인 작업이 있는지 확인하기 위해 작업 큐가 검사됩니다. 그렇다면 대기 중인 작업은 실행을 위해 메인 스레드로 들어갑니다.
예를 들어 다음 예는
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>定时机制</title> <style type="text/css"> body{ margin: 0; padding: 0; position: relative; height: 600px; } #test{ height: 30px; width: 30px; position: absolute; left: 0; top: 100px; background-color: pink; } </style> </head> <body> <p id="test"> </p> <script> var pro = document.getElementById('test'); pro.onclick = function() { alert('我没有立即被执行。'); }; function test() { a = new Date(); var b=0; for(var i=0;i<3000000000;i++) { b++; } c = new Date(); console.log(c-a); } test(); </script> </body> </html>
이 예에서는 test() 함수가 실행되는 데 약 8~9초가 걸리므로 8초 전에 이 페이지를 열고 분홍색 사각형을 클릭하면 프롬프트가 나타납니다. 상자는 바로 팝업되지는 않지만 8초가 지나야 팝업되며, 8초 전에 분홍색 상자를 여러 번 클릭하면 8초 후에 여러 번 팝업됩니다.
이 페이지를 열면 메인 스레드가 먼저 test 함수를 선언한 다음 변수 pro를 선언하고 p 노드를 pro에 할당한 다음 p 노드에 클릭 이벤트를 추가하고 콜백 함수(정지)를 지정합니다. 그런 다음 테스트 함수를 호출하고 그 안에 있는 코드를 실행합니다. 테스트 함수에서 코드를 실행하는 동안 우리는 p 노드를 클릭했습니다. 브라우저 이벤트 트리거 스레드가 이 이벤트를 감지하고 이벤트를 작업 대기열에 배치하여 메인 스레드(여기서는 테스트 함수)의 작업이 다음과 같이 되도록 했습니다. 마지막으로 작업 큐를 확인할 때 이 이벤트가 발견되고 해당 콜백 함수가 실행됩니다. 여러 번 클릭하면 트리거된 여러 이벤트가 트리거 시간에 따라 작업 대기열에 대기열에 추가됩니다(다른 요소에 클릭 이벤트를 추가하고 다른 요소를 번갈아 클릭하여 확인할 수 있음).
다음은 태스크의 작동 메커니즘을 요약한 것입니다.
비동기 실행의 작동 메커니즘은 다음과 같습니다. (동기 실행의 경우에도 마찬가지입니다. 비동기 작업이 없는 비동기 실행으로 간주될 수 있기 때문입니다.)
1. 모든 동기 작업은 메인 스레드에서 실행되어 실행 컨텍스트 스택을 형성합니다.
2. 메인 스레드 외에 "작업 대기열"도 있습니다. 비동기 작업에 실행 결과가 있는 한 이벤트는 "작업 대기열"에 배치됩니다.
3. "실행 스택"의 모든 동기화 작업이 완료되면 시스템은 "작업 대기열"을 읽어 그 안에 어떤 이벤트가 있는지 확인합니다. 해당 비동기 작업은 대기 상태를 종료하고 실행 스택에 들어가 실행을 시작합니다.
4. 메인 스레드는 위의 세 번째 단계를 계속 반복합니다.
3. 이벤트 및 콜백 함수
DOM 요소에 이벤트를 지정할 때 실제로 이벤트가 발생했을 때 해당 코드가 실행될 수 있도록 콜백 함수를 지정합니다.
메인 스레드의 이벤트 콜백 함수가 일시 중단됩니다. 작업 대기열에 해당 이벤트가 대기 중인 경우, 메인 스레드가 이를 감지하면 해당 콜백 함수가 실행됩니다. 또한 메인 스레드가 해당 콜백 함수를 실행하는 비동기 작업을 수행한다고 말할 수도 있습니다.
4. 이벤트 루프
작업 대기열의 이벤트를 확인하는 메인 스레드 프로세스는 순환적이므로 이벤트 루프의 다이어그램을 그릴 수 있습니다.
上图中主线程产生堆和执行栈,栈中的任务执行完毕后,主线程检查任务队列中由其他线程传入的发生过的事件,检测到排在最前面的事件,就从挂起的回调函数中找出与该事件对应的回调函数,然后在执行栈中执行,这个过程一直重复。
五、定时器
结合以上知识,下面探讨JavaScript中的定时器:setTimeout()和setInterval()。
setTimeout(func, t)是超时调用,间隔一段时间后调用函数。这个过程在事件循环中的过程如下(我的理解):
主线程执行完setTimeout(func, t);语句后,把回调函数func挂起,同时定时器线程开始计时,当计时等于t时,相当于发生了一个事件,这个事件传入任务队列(结束计时,只计时一次),当主线程中的任务执行完后,主线程检查任务队列发现了这个事件,就执行挂起的回调函数func。我们指定的时间间隔t只是参考值,真正的时间间隔取决于执行完setTimeout(func, t);语句后的代码所花费的时间,而且是只大不小。(即使我们把t设为0,也要经历这个过程)。
setInterval(func, t)是间歇调用,每隔一段时间后调用函数。这个过程在事件循环中的过程与上面的类似,但又有所不同。
setTimeout()是经过时间t后定时器线程在任务队列中添加一个事件(注意是一个),而setInterval()是每经过时间t(一直在计时,除非清除间歇调用)后定时器线程在任务队列中添加一个事件,而不管之前添加的事件有没有被主线程检测到并执行。(实际上浏览器是比较智能的,浏览器在处理setInterval的时候,如果发现已任务队列中已经有排队的同一ID的setInterval的间歇调用事件,就直接把新来的事件 Kill 掉。也就是说任务队列中一次只能存在一个来自同一ID的间歇调用的事件。)
举个例子,假如执行完setInterval(func, t);后的代码要花费2t的时间,当2t时间过后,主线程从任务队列中检测到定时器线程传入的第一个间歇调用事件,func开始执行。当第一次的func执行完毕后,第二次的间歇调用事件早已传入任务队列,主线程马上检测到第二次的间歇调用事件,func函数又立即执行。这种情况下func函数的两次执行是连续发生的,中间没有时间间隔。
下面是个例子:
function test() { a = new Date(); var b=0; for(var i=0;i<3000000000;i++) { b++; } c = new Date(); console.log(c-a); } function test2() { var d = new Date().valueOf(); //var e = d-a; console.log('我被调用的时刻是:'+d+'ms'); //alert(1); } setInterval(test2,3000); test();
结果:
为什么8.6秒过后没有输出两个一样的时刻,原因在上面的内容中可以找到。
执行例子中的for循环花费了8601ms,在执行for循环的过程中队列中只有一个间歇调用事件在排队(原因如上所述),当8601ms过后,第一个间歇调用事件进入主线程,对于这个例子来说此时任务队列空了,可以再次传入间歇调用事件了,所以1477462632228ms这个时刻第二次间歇调用事件(实际上应该是第三次)传入任务队列,由于主线程的执行栈已经空了,所以主线程立即把对应的回调函数拿来执行,第二次调用与第一次调用之间仅仅间隔了320ms(其实8601+320=8920,差不多就等于9秒了)。我们看到第三次调用已经恢复正常了,因为此时主线程中已经没有其他代码了,只有一个任务,就是隔一段时间执行一次间歇调用的回调函数。
用setTimeout()实现间歇调用的例子:
function test() { a = new Date(); var b=0; for(var i=0;i<3000000000;i++) { b++; } c = new Date(); console.log(c-a); } function test2(){ var d = new Date().valueOf(); console.log('我被调用的时刻是:'+d+'ms'); setTimeout(test2,3000); } setTimeout(test2,3000); test();
结果:
每两次调用的时间间隔基本上是相同。想想为什么?
再看一个例子:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Flex布局练习</title> <style type="text/css"> body{ margin: 0; padding: 0; position: relative; height: 600px; } #test{ height: 30px; width: 30px; position: absolute; left: 0; top: 100px; background-color: pink; } </style> </head> <body> <p id="test"> </p> <script> var p = document.createElement('p'); p.style.width = '50px'; p.style.height = '50px'; p.style.border = '1px solid black'; document.body.appendChild(p); alert('ok'); </script> </body> </html>
这个例子的结果是提示框先弹出,然后黑色边框的p元素才出现在页面中。原因很简单,就一句话:
在JavaScript引擎运行脚本期间,界面渲染线程都是处于挂起状态的。也就是说当使用JavaScript对界面中的节点进行操作时,并不会立即体现出来,要等到JavaScript引擎线程空闲时,才会体现出来。
上面是我整理给大家的,希望今后会对大家有帮助。
相关文章:
위 내용은 JavaScript 타이밍 메커니즘(코드 포함)에 대한 심층 설명의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!