浏览器内核(渲染进程)

打开一个Tab标签就是一个进程

一个进程包含多个线程

渲染进程里面又有:

  1. GUI渲染线程
  2. JS引擎线程
  3. 事件触发线程
  4. 定时触发器线程
  5. 异步http请求线程

GUI渲染线程和JS引擎线程互斥

JS单线程指的是主线程是单线程,在执行的过程中是可以创建额外的线程,如web worker

刚开始执行代码(JS引擎线程)

执行栈执行代码(脚本默认执行我们说是宏任务),代码里面包含同步代码和异步代码,开始分类(setTimeout,MessageChange,Promise.then),如果在代码执行过程中调用了MessageChange这个API会把回调放到微任务队列,如果代码执行过程中调用了定时器API,不会立马放到宏任务队列中,而是要等待到达时间之后才会放到宏任务队列中,即使你的定时器时间设置为0,立马放到了宏任务队列中,也不会立马就把回调执行。

主线程执行完成后,清空微任务队列

1 JS执行完成开始渲染页面

2 渲染完成之后

3 事件触发线程开始检查是否有宏任务应该执行

4 如果有宏任务则取出回调放到执行栈中执行

5 然后清空微任务队列(不是一个而是所有回调)

重复12345,这样无限循环,就是EventLoop

总结

  1. 首先先执行主栈的代码,然后清空微任务队列,然后GUI渲染
  2. GUI渲染完成之后,取一个宏任务的回调放到主线程中执行,然后清空微任务队列,然后GUI渲染
  3. 重复上述步骤

通常定时器触发之前会进程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 线程交互的方式有助于分解长运行脚本成为较短的片断。

时间分片

一个常见的长运行脚本就是循环占用了太长的运行时间。那么定时器就是你的下一个优化步骤。其基本方法是将循环工作分解到定时器序列中。典型的循环模式如下:

function processArray(items, process, callback) {
var todo = items.concat();
function updateProgress () {
if (todo.length > 0) {
process(todo.shift());
setTimeout(updateProgress, 25);
} else {
callback()
}
}
updateProgress()
}