浏览器内核(渲染进程)
打开一个Tab标签就是一个进程
一个进程包含多个线程
渲染进程里面又有:
- GUI渲染线程
- JS引擎线程
- 事件触发线程
- 定时触发器线程
- 异步http请求线程
GUI渲染线程和JS引擎线程互斥
JS单线程指的是主线程是单线程,在执行的过程中是可以创建额外的线程,如web worker
刚开始执行代码(JS引擎线程)
执行栈执行代码(脚本默认执行我们说是宏任务),代码里面包含同步代码和异步代码,开始分类(setTimeout,MessageChange,Promise.then),如果在代码执行过程中调用了MessageChange这个API会把回调放到微任务队列,如果代码执行过程中调用了定时器API,不会立马放到宏任务队列中,而是要等待到达时间之后才会放到宏任务队列中,即使你的定时器时间设置为0,立马放到了宏任务队列中,也不会立马就把回调执行。
主线程执行完成后,清空微任务队列
1 JS执行完成开始渲染页面
2 渲染完成之后
3 事件触发线程开始检查是否有宏任务应该执行
4 如果有宏任务则取出回调放到执行栈中执行
5 然后清空微任务队列(不是一个而是所有回调)
重复12345,这样无限循环,就是EventLoop
总结
- 首先先执行主栈的代码,然后清空微任务队列,然后GUI渲染
- GUI渲染完成之后,取一个宏任务的回调放到主线程中执行,然后清空微任务队列,然后GUI渲染
- 重复上述步骤
通常定时器触发之前会进程UI的渲染
Vue.nextTick(callback)
在数据变化后要执行的某个操作,而这个操作需要使用随数据改变而改变的DOM结构的时候,这个操作都应该放进Vue.nextTick()的回调函数中。
在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。
Vue 在内部对异步队列尝试使用原生的 Promise.then
、MutationObserver
和 setImmediate
,如果执行环境不支持,则会采用 setTimeout(fn, 0)
代替。
浏览器UI线程
大多数浏览器在 JavaScript 运行时停止 UI 线程队列中的任务,也就是说 JavaScript 任务必须尽快结束,以免对用户体验造成不良影响。
专家指出如果该接口在 100 毫秒内响应用户输入,用户认为自己是“直接操作用户界面中的对象。”超过 100 毫秒意味着用户认为自己与接口断开了。
由于 UI 在 JavaScript 运行时无法更新,如果运行时间长于 100 毫秒,用户就不能感受到对接口的控制。
大多数浏览器在 JavaScript 运行时停止 UI 线程队列中的任务,也就是说 JavaScript 任务必须尽快结束,以免对用户体验造成不良影响。
尽管你尽了最大努力,还是有一些 JavaScript 任务因为复杂性原因不能在 100 毫秒或更少时间内完成。这种情况下,理想方法是让出对 UI 线程的控制,使 UI 更新可以进行。让出控制意味着停止 JavaScript 运行,给 UI 线程机会进行更新,然后再继续运行 JavaScript。
Fiber 其实可以算是一种编程思想,在其它语言中也有许多应用(Ruby Fiber)。核心思想是任务拆分和协同,主动把执行权交给主线程,使主线程有时间空挡处理其他高优先级任务。当遇到进程阻塞的问题时,任务分割、异步调用和缓存策略是三个显著的解决思路。
如果一个函数运行时间太长,那么查看它是否可以分解成一系列能够短时间完成的较小的函数。
定时器与 UI 线程交互的方式有助于分解长运行脚本成为较短的片断。
时间分片
一个常见的长运行脚本就是循环占用了太长的运行时间。那么定时器就是你的下一个优化步骤。其基本方法是将循环工作分解到定时器序列中。典型的循环模式如下: