java timer是同步执行还是异步的_通杀 Event Loop面试题03-JS单线程event loop搞懂代码执行顺序 浏览器工作原理...

493bd91a7376b0abec0aa961056c0a7b.png

41239ee2063fb37fc9c3e62d9d23da20.png


单线程的含义
浏览器是 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

a25bff41011cae44a16e45a92abf54c3.png


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

a3b93878691f09655eb852ae12599a2c.png

f214849f12c44563961053380feb569b.png


浏览器中的 JavaScript 执行机制
可视化演绎深入演示:loupehttps://github.com/latentflip/loupe
// 函数执行栈演绎-->函数调用过程
function fun3() { console.log('fun3') } function fun2() { fun3(); } function fun1() { fun2(); } fun1();复制代码
两个问题问题1:如果我们在浏览器控制台中运行'foo'函数,是否会导致堆栈溢出错误?
function foo() { setTimeout(foo, 0); // 是否存在堆栈溢出错误? };复制代码
function foo() { foo() // 是否存在堆栈溢出错误? }; foo();复制代码问题2:如果在控制台中运行以下函数,页面(选项卡)的 UI 是否仍然响应
function foo() { return Promise.resolve().then(foo); };复制代码
基础题
alert(x); var x = 10; alert(x); x = 20; function x() {}; alert(x); 复制代码
浏览器端的 Event Loop
一个函数执行栈、一个事件队列和一个微任务队列。
每从事件队列中取一个事件时有微任务就把微任务执行完,然后才开始执行事件

aa9c983f9ccc9c1a2a39338e28e30ba2.png


宏任务和微任务宏任务,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 架构图

95ec0f7d78dd917127f43649b5e9e6c8.png


Node.js 中的 Event Loop

bda5fabdc6c50e2c79315a547d887e4a.png

319e2c9b149d144222999199c111d033.png

319e2c9b149d144222999199c111d033.png

fb23dfac58e7c9a3e4ff17ad07fcd159.png


Node.js的Event Loop过程:

  1. 执行全局Script的同步代码
  2. 执行microtask微任务,先执行所有Next Tick Queue中的所有任务,再执行Other Microtask Queue中的所有任务
  3. 开始执行macrotask宏任务,共6个阶段,从第1个阶段开始执行相应每一个阶段macrotask中的所有任务,注意,这里是所有每个阶段宏任务队列的所有任务,在浏览器的Event Loop中是只取宏队列的第一个任务出来执行,每一个阶段的macrotask任务执行完毕后,开始执行微任务,也就是步骤2
  4. Timers Queue -> 步骤2 -> I/O Queue -> 步骤2 -> Check Queue -> 步骤2 -> Close Callback Queue -> 步骤2 -> Timers Queue ......
  5. 这就是Node的Event Loop【简化版】

9f3ac476f064a9fe57ebd49a2bf07c32.png


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

  1. 浏览器的Event Loop和Node.js 的Event Loop是不同的,实现机制也不一样,不要混为一谈。
  2. Node.js 可以理解成有4个宏任务队列和2个微任务队列,但是执行宏任务时有6个阶段。
  3. Node.js 中,先执行全局Script代码,执行完同步代码调用栈清空后,先从微任务队列Next Tick Queue中依次取出所有的任务放入调用栈中执行,再从微任务队列Other Microtask Queue中依次取出所有的任务放入调用栈中执行。然后开始宏任务的6个阶段,每个阶段都将该宏任务队列中的所有任务都取出来执行(注意,这里和浏览器不一样,浏览器只取一个),每个宏任务阶段执行完毕后,开始执行微任务,再开始执行下一阶段宏任务,以此构成事件循环。
  4. MacroTask包括: setTimeout、setInterval、 setImmediate(Node)、requestAnimation(浏览器)、IO、UI rendering
  5. 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 the callback after 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 阶段会消耗一定时间;
所以就会出现两种情况:

  1. timer 前的准备时间超过 1ms,满足 loop->time >= 1,则执行 timer 阶段(setTimeout)的回调函数
  2. 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)) 复制代码
运行结果如下:

773385dab3e341cc126529e1741c40f5.png


运行时分析

e395b7b0932ce291290aa1012fdef7d0.png


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'));复制代码
浏览器执行结果:

f0d954e46a9dd634a604194bd51f3fa2.png


低于Node 11的版本

60978a76fdc751615950a3381d6eb92f.png


Node 11+

ef27b3ac677800c1ee0bd17908a209da.png


向浏览器运行结果靠齐

e2c5554de8aca2c0b67bc0aa84d43730.png

990e7736e2c2867764e178bad285724e.png


参考资料:
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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值