进程和线程
keywords
: 程序运行所需要的专属内存空间叫做进程,在进程中运行代码的[人]叫做线程
程序运行所需要专属的内存空间
,可以把这块内存空间简单的理解为进程
运行代码的「人」称之为「线程」。
一个进程至少有一个线程,所以在进程开启后会自动创建一个线程来运行代码,该线程称之为主线程。
浏览器有哪些进程和线程?
- 浏览器进程
- 网络进程:加载网络资源
- 渲染进程:渲染进程启动后,会开启一个渲染主线程,主线程负责执行 HTML、CSS、JS 代码。默认每个标签页都会有一个渲染进程
谈谈事件循环吧
keywords
: 浏览器渲染主线程的工作方式
事件循环又叫做消息循环,是浏览器渲染主线程的工作方式。
在 Chrome 的源码中,它开启一个不会结束的 for 循环,每次循环从消息队列中取出第一个任务执行,而其他线程只需要在合适的时候将任务加入到队列末尾即可。
过去把消息队列简单分为宏队列和微队列,这种说法目前已无法满足复杂的浏览器环境,取而代之的是一种更加灵活多变的处理方式。
根据 W3C 官方的解释,每个任务有不同的类型,同类型的任务必须在同一个队列,不同类型的任务可以属于不同的队列。不同任务队列有不同的优先级,在一次事件循环中,由浏览器自行决定取哪一个队列的任务。但浏览器必须有一个微队列,微队列的任务一定具有最高的优先级,必须优先调度执行。
如何理解 JS 的异步?
keywords
: 渲染主线程只有一个、某些工作同步执行会导致阻塞
JS是一门单线程的语言,这是因为它运行在浏览器的渲染主线程中,而渲染主线程只有一个。
渲染主线程承担着诸多的工作,如果都使用同步的方式,就极有可能导致主线程产生阻塞,所以浏览器采用异步的方式来避免。
具体做法是当某些任务发生时,将任务交给其他线程去处理,自身立即结束任务的执行,转而执行后续代码。当其他线程完成时,将事先传递的回调函数包装成任务,加入到消息队列的末尾排队,等待主线程调度执行
常见宏任务
- 定时器任务:
setTimeout/setInterval
- 页面渲染
- 事件回调 I/O操作
- 网络请求
setImmediate()
(node.js环境)
常见微任务
Promise
回调函数Async/await
MutationObsever
回调函数process.nextTick
浏览器事件循环的流程是什么?
- 每次循环都会检查消息队列中是否有任务。如果有,就拿出队列中的第一个任务执行,如果没有,则进入休眠状态。
- 当其他线程把异步任务处理完成,就会将后续的回调操作包装成新任务,加入消息队列末尾,等待渲染主线程拿取执行。
其中,微任务消息队列优先级最高,浏览器渲染主线程会优先拿去,其次是其他消息队列
NodeJS 事件循环的流程是什么?
nodejs 中的异步代码会交给 libuv 执行,同步代码则在主线程中执行。
timers
计时器队列:保存setTimeout
,setInterval
相关回调pending callback
阶段:调用上一次事件循环没在poll
阶段立刻执行,而延迟的 I/O 回调函数idle
,prepare
阶段:node内部使用poll
轮询队列:处理除timers
和check
队列外的绝大多数 I/O 回调任务check
检查队列:负责处理setImmediate
定义的回调函数close callbacks
: 执行所有注册close
事件的回调函数
在上面过程中任意一个阶段遇到微任务则先清空微任务队列。
微任务队列包括:nextTick
, Promise
;
nextTick
优先级更高
setTimeout(() => { console.log('setTimeout'); }, 0)
setImmediate(() => { console.log('setImmediate'); })
每次执行到 timers
队列时,定时器观察者内部会去检查代码中的定时器是否超过定时时间,而 setImmediate
则是直接将回调任务加入到 check
队列。
所以总的来说,setImmediate
的执行效率要远高于 setTimeout
。所有上诉代码setTimeout
不一定先于setImmediate
加入队列,无法预测结果。
原文链接:https://juejin.cn/post/7350880189837164580 作者:xiyueyezibile