이 글은 주로 브라우저와 NodeJS 간의 EventLoop 유사점과 차이점을 소개하며, 참고할만한 가치가 있습니다. 이제 도움이 필요한 친구들이 참고할 수 있도록 공유하겠습니다.
javascript는 단일 스레드 스크립팅 언어이지만 개발자가 스레드 차단 문제를 해결하는 데 도움이 되는 많은 비동기 API가 있습니다. 예: onClick 등록 콜백 함수, 필수 ajax 등... 하지만 JavaScript 실행 환경은 스레드를 항상 차단하고 작업을 계속 실행하기 전에 다양한 비동기 작업이 완료될 때까지 기다리지 않고 어떻게 단일 스레딩을 달성합니까?
답은: event loop
1.event loop 的规范是在HTML5中规定的。 2.event loop 是 javascript 运行环境(手动加粗) 的机制。 3.浏览器实现的event loop 与 NodeJS 实现的event loop 是有异同的。
HTML5에 정의된 이벤트 루프 사양 링크 https://www.w3.org/TR/html5/w...
브라우저 이벤트 루프
1.
이벤트 루프에 대한 간략한 이해는 이벤트 루프입니다. Ruan Yifeng 선생님의 블로그에는 그림이 있습니다. 비록 매우 간단하고 명확하지만 일부 내용이 부족하고 이벤트 루프의 전반적인 순환 메커니즘을 완전히 보여줄 수 없습니다. 먼저 사진을 살펴보겠습니다.
사진은 작성자의 원본이 아니며 Ruan Yifeng의 블로그에서 가져온 것입니다. 해당 사진은 저작권이 침해되어 삭제되었습니다.
그림에서 얻을 수 있는 정보는 다음과 같습니다.
1. 실행 중이고 실행을 기다리는 다양한 이벤트가 포함된 스택이 하나뿐이므로 javascript 엔진은 단일 스레드에서 javascript를 실행합니다.
2. 일부 webAPI는 실행 중에 생성된 콜백을 대기열, 즉 "이벤트 대기열"에 넣습니다.
3. 이벤트 루프에서는 "이벤트 큐"에서 실행을 기다리는 이벤트가 지속적으로 자바스크립트 실행 스택으로 푸시됩니다.
이것이 이벤트 루프 단순화의 메커니즘입니다. 왜 단순화라고 할까요? 루프에서 언급되지 않은 연산과 규칙이 많기 때문입니다.
밤은 안 드리고 비유를 할게요.
자주하는 질문에 대해 이야기해보겠습니다. (글 편집하기 불편해서 한줄만 하고, 새줄 어기면 때리겠습니다!)
setTimeout(e=>{ console.log(1) },0); new Promise((res,rej)=>{ res() }).then(e=>{ console.log(2) });
역시 입니다 javascript에서 제공하는 비동기 API와 동일합니다. 직접 실행되지만(개발자가 원하는 대로 차단 및 레버리지 방지로 인해 지연이 발생하지만) 이 두 줄의 코드를 누가 입력하거나 종료하든 출력은 다음과 같습니다. 2 1. 이는 이벤트 루프의 매크로 태스크 및 마이크로 태스크의 실행 순서와 규칙을 포함하기 때문입니다.
2. 전체 프로세스
방금 언급한 흐름도가 완벽하지 않다는 문제로 돌아가서 이제 완전하고 포괄적인 이벤트 루프 흐름 차트를 살펴보겠습니다.
사진은 작성자의 원본이 아니며 자바스크립트 닌자의 비밀에서 나온 사진이므로 침해되거나 삭제되지 않습니다.
이것은 이벤트 루프의 전체 흐름도입니다. 지금은 언급되지 않은 많은 명사를 볼 수 있습니다. 처음부터 끝까지(위에서 아래로) 읽어보세요. 매크로태스크 큐 태스크. 두 가지 상황이 있습니다.
(문서에서 수동 + 굵은 글씨) 작업을 javascript 실행 스택에 푸시하고 실행합니다. 하향
(설명서 + 기사 추가) 거친) Microtask 대기열이 빌 때까지. 직설적으로 말하면: 모든 작업을 이 작업 대기열부터 순서대로 자바스크립트 실행 스택에 푸시하고 아래쪽으로 실행합니다
여부를 결정합니다. (문서에서 매뉴얼 + 굵은 글씨체) ) 필요, 가능 여부 UI 업데이트 [이 주기 시간 문제는 나중에 언급하겠습니다]아니요, 첫 번째 단계를 반복합니다
이 시점에서 완전한 이벤트 루프 프로세스는 다음과 같습니다. 완전히 끝났습니다.Macrotask queue --> setTimeout || setInterval || javascript代码 Microtask queue --> Promise.then()로그인 후 복사
3.实例解析
什么鬼?这么复杂? 弄懂?不存在的
现在回到刚才提到的 “老生常谈的问题” 从实例的角度来说明一下问题。我们假设这个 javascript 文件叫做 "main.js"
"main.js"中的代码(+ 为自定义标记)
+1 console.log(1); +2 setTimeout(e=>{ console.log(2); },0) +3 setTimeout(e=>{ console.log(3); },0) +4 new Promise((resolve,reject)=>{ console.log(4); resolve();}) .then(e=>{ console.log(5); }) +5 setTimeout(e=>{ console.log(6); +6 new Promise((resolve,reject)=>{ console.log(7); resolve(); }) .then(e=>{ console.log(8);}) })
那么这个执行顺序是怎样呢?从头带尾梳理一遍(词穷,全文只要是流程统一是“从头到尾梳理一遍”)
macrotask: javascript 代码,所有同步代码执行。输出:1 4。注册 +4 到 microtask。 注册+2 +3 +5 到 macrotask。
microtask: 执行 +4 输出:5。
macrotask: 执行 +2。 输出 2。
microtask: 无
macrotask: 执行 +3。 输出 3。
microtask: 无
macrotask: 执行 +5。 输出 6 7。 注册 +6 到 microtask。
microtask: 输出 8。
所以总体输出的顺序为:1 4 5 2 3 6 7 8
如果这个输出与你所想相同,那么基本就没有问题了。
那么如果不对或者有问题怎么办?
PS: 前面提到 【本次循环耗时】这个问题,这里我也不是非常清楚,望大牛指点。浏览器一般渲染页面60/S,以达到每秒60帧(60 fps),所以大概16ms一次,既然有了时间我们不经就会问?前面的任务处理耽误了则么办?因为javascript线程与UI线程互斥,某些任务导致 javascript引擎 坑了队友,自然而然没法在16ms的节点上到达这一步,从secrets of javascript ninja中了解到,一般会摒弃这次渲染,等待下一次循环。( 如有问题请指正! )
浏览器中的 event loop 到此结束,下面说说 NodeJS 的 event loop
二 NodeJS的event loop
NodeJS 的 event loop 也是有 Macrotask queue 与 Microtask queue 的。只不过 NodeJS 的略有不同。那么主要说说不同在哪里。
NodeJS中 Macrotask queue 与 Microtask queue 实例化到API为: Macrotask queue --> script(主程序代码),setImmediate, I/O,setTimeout, setInterval Microtask queue --> process.nextTick, Promise
1.Macrotask queue 不同之处
上面说到了浏览器 event loop 的 Macrotask queue 在每次循环中只会读取一个任务,NodeJS 中 Macrotask queue 会一次性读取完毕( 同阶段的执行完毕,后面会说到Macrotask queue 分为 6个阶段 ),然后向下读取Microtask。
注意: 这一条与 NodeJS版本有很大关系,在看 深入浅出NodeJS 这一本书时( 看的版本很旧,不知是否有修订版,如有请告知。 ),提到的 setImmediate 每次循环只会执行一次,并且给出的示例在 v8.9.1 版本跑时已不符合书中所写。书中示例如下(+ 为自定义标记,原文中没有):
+1 process.nextTick(function () { console.log('nextTick执行1'); }); +2 process.nextTick(function () { console.log('nextTick执行2'); }); +3 setImmediate(function () { console.log('setImmediateჽ执行1'); +4 process.nextTick(function () { console.log('强势插入'); }); }); +5 setImmediate(function () { console.log('setImmediateჽ执行2'); }); +6 console.log('正常执行'); 正常执行 nextTick执行1 nextTick执行2 setImmediate执行1 强势插入 setImmediateჽ执行2
在 v8.9.1 中截图如下
从图片中可以看到,至少在 v8.9.1 版本中 Macrotask queue 会直接全部执行。按照惯例从头到尾的梳理一遍:
macrotask: javascript 代码,所有同步代码执行。输出:正常执行。注册 +3 +5 到 Macrotask。执行process.nextTick(),最终输出:正常执行, nextTick执行1, nextTick执行2。
**microtask: 无
macrotask: 执行 +3 +5。 输出:setImmediate执行1, setImmediateჽ执行2。 执行process.nextTick(),最终输出:setImmediate执行1, setImmediateჽ执行2,强势插入。
microtask: 无
所以最终输出为:正常执行, nextTick执行1, nextTick执行2,setImmediate执行1, setImmediateჽ执行2,强势插入。
2.process.nextTick(),setImmediates,以及event loop的6个阶段
NodeJS 中 Macrotask queue会分为 6 个阶段,每个阶段的作用如下(process.nextTick()在6个阶段结束的时候都会执行):
timers:执行setTimeout() 和 setInterval()中到期的callback。 I/O callbacks:上一轮循环中有少数的I/Ocallback会被延迟到这一轮的这一阶段执行 idle, prepare:仅内部使用 poll:最为重要的阶段,执行I/O callback,在适当的条件下会阻塞在这个阶段 check:执行setImmediate的callback close callbacks:执行close事件的callback,例如socket.on("close",func)
注:此6个阶段非笔者原创来自 https://cnodejs.org/topic/5a9...,文章从底层C代码分析NodeJS event loop。这里做只做简单整合。侵删。
在了解了这六个阶段后,我们可以发现定时器系列在NodeJS event loop中 Macrotask queue 读取顺序为:
1. setTimeout(fun,0) setInterval(fun,0) 2. setImmediate
空口无凭,在实例中了解。的代码奉上( 代码较长,分为三段,方便阅读,避免滚动。 ):
+1 process.nextTick(function(){ console.log("1"); }); +2 process.nextTick(function(){ console.log("2"); +3 setImmediate(function(){ console.log("3"); }); +4 process.nextTick(function(){ console.log("4"); }); }); +5 setImmediate(function(){ console.log("5"); +6 process.nextTick(function(){ console.log("6"); }); +7 setImmediate(function(){ console.log("7"); }); });
+8 setTimeout(e=>{ console.log(8); +9 new Promise((resolve,reject)=>{ console.log(8+"promise"); resolve(); }).then(e=>{ console.log(8+"promise+then"); }) },0) +10 setTimeout(e=>{ console.log(9); },0) +11 setImmediate(function(){ console.log("10"); +12 process.nextTick(function(){ console.log("11"); }); +13 process.nextTick(function(){ console.log("12"); }); +14 setImmediate(function(){ console.log("13"); }); });
console.log("14"); +15 new Promise((resolve,reject)=>{ console.log(15); resolve(); }).then(e=>{ console.log(16); })
这么复杂的异步嵌套在一起是不是很头疼呢?
我!不!看!了!
마지막 빗질, 가장 크고 가장 완벽한 빗질입니다. 고대부터처음부터 끝까지 샅샅이 뒤져보세요
macrotask: 자바스크립트 코드, 모든 동기 코드 실행. 출력: 14. process.nextTick() 실행, 최종 출력: 14, 15, 1, 2, 4. 매크로태스크에 +3 +5 +8 +11을 등록하세요. Microtask에 +15로 가입하세요.
microtask: 실행 +15 출력 16
macrotask: 실행 +8 +10 출력 8, 8promise, 9. Microtask에 +9로 가입하세요.
microtask: +9를 실행하고 8promise+then
macrotask: +5 +11 +3을 실행하고 5, 10, 3을 출력합니다. macrotask에 +7 +14를 등록하세요. process.nextTick() 실행, 최종 출력: 5 10 3 6 11 12.
마이크로태스크: 없음
매크로태스크: 실행 +7 +14. 출력: 7, 13
microtask: 없음
따라서 총 출력은 다음과 같습니다. 14, 15, 1, 2, 4, 8, 8promise, 9, 8promise+then, 5, 10, 3 , 6, 11, 12, 7, 13
세 끝
이것이 끝입니다. 브라우저와 NodeJS 이벤트 루프가 모두 분석되었습니다. 이 과정에서 Ruan Yifeng의 블로그, Zhihu 및 CSDN 기사의 일부 내용이 인용 및 삭제되었습니다.
최근에 저는 몇 가지 기본 지식을 이해하면서 많은 것을 얻었습니다. 여기에는 .... 그리고 모든 종류의 이상한 질문이 포함됩니다. 시간이 나면 적어 보겠습니다.
위 내용은 이 글의 전체 내용입니다. 모든 분들의 학습에 도움이 되었으면 좋겠습니다. 더 많은 관련 내용은 PHP 중국어 홈페이지를 주목해주세요!
관련 권장 사항:
위 내용은 브라우저와 NodeJS 간 EventLoop의 유사점, 차이점 및 부분적 메커니즘의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!