两个字*优秀*优质*android文Java程序员们的(家)

2019-08-12

2020-09-25  本文已影响0人  传奇内服号

单线程的含义

浏览器是 multi-process,一个浏览器只有一个 Browser Process,负责管理 Tabs、协调其他 process 和 Renderer process 存至 memory 内的 Bitmap 绘制到页面上的(pixel);在 Chrome中,一个 Tab 对应一个 Renderer Process,Renderer process 是 multi-thread,其中 main thread 负责页面渲染(GUI render engine)执行 JS (JS engine)和 event loop;network component 可以开2~6个 I/O threads 平行去处理。

Structure of a Web Browser

need-to-insert-img

主线程,JS执行线程,UI渲染线程关系如下图所示:

浏览器中的 JavaScript 执行机制

可视化演绎

深入演示:loupe

https://github.com/latentflip/loupe

// 函数执行栈演绎-->函数调用过程

functionfun3() {    console.log('fun3')}functionfun2() {    fun3();}functionfun1() {    fun2();}fun1();复制代码

两个问题

问题1:如果我们在浏览器控制台中运行'foo'函数,是否会导致堆栈溢出错误?

functionfoo() {setTimeout(foo, 0); // 是否存在堆栈溢出错误?};复制代码

functionfoo() {  foo() // 是否存在堆栈溢出错误?};foo();复制代码

问题2:如果在控制台中运行以下函数,页面(选项卡)的 UI 是否仍然响应

functionfoo() {returnPromise.resolve().then(foo);};复制代码

基础题

alert(x);  var x = 10;alert(x);  x = 20;functionx() {}; alert(x);复制代码

浏览器端的 Event Loop

一个函数执行栈、一个事件队列和一个微任务队列。

每从事件队列中取一个事件时有微任务就把微任务执行完,然后才开始执行事件

宏任务和微任务

宏任务,macrotask,也叫tasks。一些异步任务的回调会依次进入macro task queue,等待后续被调用,这些异步任务包括:

setTimeout

setInterval

setImmediate (Node独有)

requestAnimationFrame (浏览器独有)

I/O

UI rendering (浏览器独有)

微任务,microtask,也叫jobs。另一些异步任务的回调会依次进入micro task queue,等待后续被调用,这些异步任务包括:

process.nextTick (Node独有)

Promise.then()

Object.observe

MutationObserver

(注:这里只针对浏览器和NodeJS)

注意:Promise构造函数里的代码是同步执行的。

基础题

setTimeout(()=> {    console.log(1)    Promise.resolve(3).then(data => console.log(data))}, 0)setTimeout(()=> {    console.log(2)}, 0)复制代码

可视化演绎

console.log('script start');setTimeout(function() {  console.log('setTimeout');}, 0);Promise.resolve().then(function() {  console.log('promise1');}).then(function() {  console.log('promise2');});复制代码

浏览器端:jakearchibald.com/2015/tasks-…

巩固提高题

console.time("start")setTimeout(function() {    console.log(2);}, 10);new Promise(function(resolve) {    console.log(3);    resolve();    console.log(4);}).then(function() {    console.log(5);    console.timeEnd("start")});console.log(6);console.log(8);requestAnimationFrame(() => console.log(9))复制代码

Node.js 架构图

Node.js 中的 Event Loop

need-to-insert-img

need-to-insert-img

Node.js的Event Loop过程:

执行全局Script的同步代码

执行microtask微任务,先执行所有Next Tick Queue中的所有任务,再执行Other Microtask Queue中的所有任务

开始执行macrotask宏任务,共6个阶段,从第1个阶段开始执行相应每一个阶段macrotask中的所有任务,注意,这里是所有每个阶段宏任务队列的所有任务,在浏览器的Event Loop中是只取宏队列的第一个任务出来执行,每一个阶段的macrotask任务执行完毕后,开始执行微任务,也就是步骤2

Timers Queue -> 步骤2 -> I/O Queue -> 步骤2 -> Check Queue -> 步骤2 -> Close Callback Queue -> 步骤2 -> Timers Queue ......

这就是Node的Event Loop【简化版】

浏览器端和 Node 端有什么不同

浏览器的Event Loop和Node.js 的Event Loop是不同的,实现机制也不一样,不要混为一谈。

Node.js 可以理解成有4个宏任务队列和2个微任务队列,但是执行宏任务时有6个阶段。

Node.js 中,先执行全局Script代码,执行完同步代码调用栈清空后,先从微任务队列Next Tick Queue中依次取出所有的任务放入调用栈中执行,再从微任务队列Other Microtask Queue中依次取出所有的任务放入调用栈中执行。然后开始宏任务的6个阶段,每个阶段都将该宏任务队列中的所有任务都取出来执行(注意,这里和浏览器不一样,浏览器只取一个),每个宏任务阶段执行完毕后,开始执行微任务,再开始执行下一阶段宏任务,以此构成事件循环。

MacroTask包括: setTimeout、setInterval、 setImmediate(Node)、requestAnimation(浏览器)、IO、UI rendering

Microtask包括: process.nextTick(Node)、Promise.then、Object.observe、MutationObserver

注意:new Promise() 构造函数里面是同步代码,而非微任务。

面试常考细节

微任务有两种 nextTick和 then 那么这两个谁快呢?

Promise.resolve('123').then(res=>{  console.log(res)})process.nextTick(() => console.log('nextTick'))复制代码

//顺序 nextTick 123

//很明显 nextTick快

解释:

promise.then 虽然和 process.nextTick 一样,都将回调函数注册到 microtask,但优先级不一样。process.nextTick 的 microtask queue 总是优先于 promise 的 microtask queue 执行。

setTimeout 和 setImmediate

setImmediate(callback[, ...args])

Schedules the "immediate" execution of thecallbackafter I/O events' callbacks.

setImmediate()方法用于中断长时间运行的操作,并在完成其他操作后立即运行回调函数。

setTimeout 和 setImmediate 执行顺序不固定 取决于node的准备时间

setTimeout(() => {    console.log('setTimeout')}, 0)setImmediate(() => {    console.log('setImmediate')})复制代码

运行结果:

setImmediate

setTimeout

或者:

setTimeout

setImmediate

为什么结果不确定呢?

解释:

setTimeout/setInterval 的第二个参数取值范围是:[1, 2^31 - 1],如果超过这个范围则会初始化为 1,

即 setTimeout(fn, 0) === setTimeout(fn, 1)。

我们知道 setTimeout 的回调函数在 timer 阶段执行,setImmediate 的回调函数在 check 阶段执行,event loop 的开始会先检查 timer 阶段,但是在开始之前到 timer 阶段会消耗一定时间;

所以就会出现两种情况:

timer 前的准备时间超过 1ms,满足 loop->time >= 1,则执行 timer 阶段(setTimeout)的回调函数

timer 前的准备时间小于 1ms,则先执行 check 阶段(setImmediate)的回调函数,下一次 event loop 执行 timer 阶段(setTimeout)的回调函数。

setTimeout(() => {    console.log('setTimeout')}, 0)setImmediate(() => {    console.log('setImmediate')})const start = Date.now()while(Date.now() - start < 10);复制代码

运行结果一定是:

setTimeout

setImmediate

const fs = require('fs')fs.readFile(__filename, () => {setTimeout(() => {        console.log('setTimeout')    }, 0)setImmediate(() => {        console.log('setImmediate')    })})复制代码

运行结果:

setImmediate

setTimeout

解释:

fs.readFile 的回调函数执行完后:

注册 setTimeout 的回调函数到 timer 阶段

注册 setImmediate 的回调函数到 check 阶段

event loop 从 pool 阶段出来继续往下一个阶段执行,恰好是 check 阶段,所以 setImmediate 的回调函数先执行

本次 event loop 结束后,进入下一次 event loop,执行 setTimeout 的回调函数

所以,在 I/O Callbacks 中注册的 setTimeout 和 setImmediate,永远都是 setImmediate 先执行。

巩固提高题目

console.time("start")setTimeout(function() {    console.log(2);}, 10);setImmediate(function() {    console.log(1);});new Promise(function(resolve) {    console.log(3);    resolve();    console.log(4);}).then(function() {    console.log(5);    console.timeEnd("start")});console.log(6);process.nextTick(function() {    console.log(7);});console.log(8);// requestAnimationFrame(() => console.log(9))复制代码

运行结果如下:

运行时分析

Node 11.x + 新变化

setTimeout(() => console.log('timeout1'));setTimeout(() => {    console.log('timeout2')    Promise.resolve().then(() => console.log('promise resolve'))});setTimeout(() => console.log('timeout3'));setTimeout(() => console.log('timeout4'));复制代码

浏览器执行结果:

低于Node 11的版本

Node 11+

向浏览器运行结果靠齐

参考资料:

github.com/nodejs/node…MacroTask and MicroTask execution order

blog.insiderattack.net/new-changes…

github.com/nodejs/node…timers: run nextTicks after each immediate and timer

作者:自由如风FC

链接:https://juejin.im/post/5d50d2e3e51d4561ea1a941f

来源:掘金

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

上一篇下一篇

猜你喜欢

热点阅读