浏览器的循环系统
消息队列和事件循环系统
系统的逐步完善,与解决问题的过程
单线程可以处理好确定的任务,但是无法处理新增的任务
采用事件循环监听是否有新的事件,但是无法处理其他线程的任务
引入消息队列,IO 线程产生的任务添加到消息队列,渲染主线程循环读取消息队列并执行
渲染主线程会有专门的 IO 线程,用来接受其他进程的的消息
然而,消息队列中有非常多的消息类型:
输入事件(鼠标滚动点击等等)、微任务、文件读写、Websocket、js 定时器等等。
还有页面相关的 JS 执行、解析 DOM、样式布局计算、css 动画。
这些任务都是在主线程执行的。
单线程还会有问题
如何处理高优先级任务,比如 DOM 的频繁变化
使用微任务权衡,消息队列的每个任务为一个宏任务,每个宏任务包含一个微任务队列
如何解决单任务长时间占据主线程
Js 采用回调功能避免
setTimeout 是如何实现的
除了正常使用的消息队列,还维护了一个延迟消息队列。
- js 调用 setTimeout ,渲染进程会创建回调任务,包含倒计时时间等信息,并放入延迟消息队列。
- 当前的任务执行完后,会查看延迟消息队列,一次执行已经到期的队列。
使用注意事项
长时间的任务会阻塞 setTimeout
setTImeout 被嵌套调用 5 次以上后,
未激活页面,最小执行间隔 1000 毫秒
为了优化后台页面的消耗
执行延迟最大时间
浏览器以32bit存储,所以最大是 2147483647 毫秒,超过会溢出,立即执行
回调 this 不符合直觉,使用 箭头函数,或者 bind
XMLHttpRequest 是如何实现的
异步回调函数
- 把异步函数做成一个任务,添加到消息队列队尾
- 把异步函数添加到微任务队列
XMLHttpRequest 运作过程
- 创建 XMLHttpRequest 对象
- 注册回调函数,
onerror
、ontimeout
、onreadystatechange
- 配置请求信息
xhr.send
发送请求
微任务和宏任务
宏任务
- 渲染事件(解析 DOM、计算布局、绘制)
- 用户交互事件(鼠标点击、滚动页面、resize)
- JS 脚本执行事件
- 网络请求完成、文件读写完成事件
- 先从多个消息队列中选出一个最老的任务,这个任务称为oldestTask;
- 然后循环系统记录任务开始执行的时间,并把这个oldestTask设置为当前正在执行的任务;
- 当任务执行完成之后,删除当前正在执行的任务,并从对应的消息队列中删除掉这个oldestTask;
- 最后统计执行完成的时长等信息。
微任务
产生微任务有两种方式
- MutationObserver 监控 DOM 节点
- 使用 promise
MutationObserver采用了“异步+微任务”的策略。
- 通过异步操作解决了同步操作的性能问题;
- 通过微任务解决了实时性的问题。
Promise
- Promise中为什么要引入微任务?
- Promise中是如何实现回调函数返回值穿透的?
- Promise出错后,是怎么通过“冒泡”传递给最后那个捕获异常的函数?