事件循环

48 阅读6分钟

浏览器进程模型

进程:程序运行需要有自己的内存空间,可以把这块空间理解为进程。

每一个应用至少应该有一个进程,进程之间相互独立,通信需要双方同意。

线程:有了进程可以运行程序的代码,运行代码的地方称之为线程。

一个进程至少有一个线程,进程开启后会自动创建一个线程用来运行代码,这个线程称为主线程。 如果需要同时执行多块代码,主线程会启动更多的线程来执行代码,所以一个进程可以包含多个线程。

浏览器的进程和线程

浏览器是一个多进程和多线程的应用程序。

为了减少连环崩溃的概率,当启动浏览器后,它会自动启动多个进程。

浏览器进程:主要负责浏览器界面的展示,用户的交互,子进程管理等,浏览器进程内部会启动多个线程处理不同的任务。

网络进程:负责网络资源加载,网络进程会启动多个线程处理不同的网络任务。

渲染进程:渲染进程启动后,会开启一个渲染主线程,主线程负责执行HTML、css、js代码,默认情况下浏览器会为每个标签页开启一个新的渲染进程,以保证标签页不会互相影响。

未来会将一个站点使用同一个进程

如何查看浏览器进程:更多工具-> 任务管理器,一个标签页代表一个渲染进程

渲染主线程如何工作

  • 渲染主线程是浏览器中最繁忙的线程:
  • 解析HTML
  • 解析css
  • 计算样式
  • 布局
  • 处理图层
  • 屏幕刷新,fps
  • 执行js代码
  • 执行事件处理函数
  • 执行计时器的回调函数
  • ......

渲染主线程采用排队的方式解决多任务执行,当我们触发事件时浏览器会将我们的任务放入消息队列中,等待执行。

主线程执行任务,将待执行任务放入消息队列(message queue)中。

在开始的时候,渲染主线程会进入一个无限循环。

每一次循环会检查队列中是否有待执行任务,如果有,就取第一个任务,执行完进入下一次循环,如果没有则进入休眠状态。

其他所有线程(包括其他进程的线程)可以随时向消息队列中添加任务,新任务会加到消息队列的末尾,在添加新任务时,如果主线程时休眠状态,则会将其唤醒以继续循环拿任务。

整个过程,为事件循环。

异步

在代码执行时,会遇到无法立即处理的任务:

计时完成后需要执行的任务 -- setTimeOut、setInterval

网络通信完成后需要执行的任务 -- XHR fetch

用户操作后需要执行的任务 -- addEventListener

如果让渲染主线程等待这些任务的时机达到,就会导致主线程阻塞,导致浏览器卡死。

浏览器通过异步的方式解决这种阻塞的问题。

如何理解js的异步

js是单线程语言,这是因为它运行在浏览器的渲染主线程中,而渲染主线程只有一个

主线程承担很多工作,渲染页面、执行js都在其中

如果使用同步则会导致阻塞,一方面会导致白白消耗时间,另一方面导致页面无法及时更新,给用户造成卡死的现象 所以浏览器采用异步的方式进行避免,具体做法是:

当任务发生时,比如计时器、网络,事件监听,主线程将任务交给其他线程处理,自身立即结束任务的执行,转而执行后续代码,当其他线程完成时,将事先传递的回调函数包装成任务,加入消息队列的末尾排队,等待主线程的执行

任务的优先级

任务没有优先级,先进先出 但消息队列是有优先级的

根据W3C的最新解释:

每个任务都有一个任务类型,同一个任务类型必须在一个队列。

不同类型的任务可以分属于不同的队列。在一次事件循环中,浏览器可以根据实际情况从不同的队列中取出任务执行。

浏览器必须准备好一个微队列,微队列中的任务优先所有其他任务执行。

目前谷歌的实现包含下面的队列:

延时队列:用于存放计时器到达后的回调任务,优先级【中】

交互队列:用于存放用户操作后产生的事件处理任务,优先级【高】

微队列:用于存放需要最快执行的任务,优先级【最高】

添加任务到微队列的主要方式是使用Promise、MutationObserver

解释js的事件循环

事件循环又叫做消息循环,是浏览器渲染主线程的工作方式

在chrome源码中,会开启一个不会结束的 for 循环,每次循环从消息队列中取出第一个任务执行,而其他线程只需要在合适的时机将任务推到队列末尾

过期把消息队列简单分为宏队列和微队列,这种说法目前已经无法满足复杂的浏览器环境,取而代之的是一种更加灵活多变的处理方式。

根据W3C的解释,每个任务有不同的类型,同类型的任务必须在同一个队列,不同的任务可以属于不同的队列。不同任务队列有不同的优先级,在一次事件循环中,由浏览器自行决定取哪个队列的任务。但浏览器必须有一个微队列,微队列的任务一定具有最高的优先级,必须优先执行。

js的计时器可以做的精准计时吗,为什么?

不能

因为计算机硬件没有原子钟,无法做到精确计时

操作系统的计时函数本身就有少量偏差,由于js的是计时器最终调用的就是操作系统的函数,所以也会有偏差

按照W3C的标准,浏览器实现计时器时,如果嵌套层级超过5ceng,则会有4毫秒的最少时间,这样在计时时间少于4毫秒时又带来偏差

受事件循环的影响,计时器的回调函数只能在主线程空闲时运行,又带来偏差