草庐IT

浏览器事件循环相关概念及其理解

哇衡 2023-03-28 原文

相关解释

​ 在打开浏览器的时候会创建出来若干进程,以便于完成相关任务,其实最重要的是渲染进程

渲染进程的主要工作为:解析HTML,绘制CSS样式,执行JS代码等。

​ 其中在执行JS代码的时候,会根据代码任务的类型创建出来若干队列,其中常见的有:

  1. 延时队列(setInterval,setTimeout...)
  2. 交互队列(click,resize,scroll...)
  3. 微队列(Promise,MutationObserver...)

​ 但是,在执行代码的时候,所有的代码也就是被称之任务的是不分优先级的。也就是说在一个js文件中,依旧按照从上到下的顺序去解析执行代码。

​ 然后在执行代码的时候会将不普通的任务进行分类,放到相应的队列中。比如在执行到延迟函数(setInterval,setTimeout)的时候就会将任务放到延时队列中,遇到Promise函数就将任务放到微队列中。

虽然代码任务是不分优先级的,但是队列是分优先级的,其中微队列的优先级最高,交互队列比延时队列优先级高

​ 在渲染主线程从上到下执行完所有的代码后,将会从不同的队列中读取任务继续执行。在从队列中读取的时候,微队列是最先被读取的。其次才是其他队列。

示例 1:

setTimeout(function() {
    console.log(1)
}, 0)

console.log(2)

输出顺序是:2 1

​ 因为渲染进程在执行的时候会从上到下依次运行,首先是运行到setTimeout计时器,这时候会有有一个负责计时的进程进行该任务,但是因为计时时间是0,所以会将回调函数所对应的任务放入延时进程中,与此同时,渲染进程仍然在继续进行中,紧接着就执行到了console.log(2),因此2将会被输出,然后所有的js代码都被执行完了,渲染进程会先去微队列中查询是否有任务,发现没有就去延时队列中,发现有任务,于是就执行该任务,所以输出了1

示例 2:

function delay(duration) {
    var start = Date.now()
    while(Date.now() - start < duration) {};
}

setTimeout(function() {
    console.log(1)
}, 0)

delay(2000)

console.log(2)

输出顺序是:2 1

​ 首先是定义了一个普通的函数,然后遇到了计时器,因此会将该计时器结束后的任务放入到延时队列中,然后调用了delay函数,并且传入了2000,该函数的作用是延迟duration时间,在这个函数中虽然是一个什么都没干的循环,但是渲染进程仍要等待相应的时间,因为什么都没干是编写人员的想法,但是渲染进程需要去在相应的时间进行执行,然后在duration时间之后,才遇到了console.log(2),然后执行完了所有的代码之后,渲染进程微队列读取,去延时队列读取,才执行了console.log(1)

示例3:

setTimeout(function() {
    console.log(1)
}, 0)

Promise.resolve().then(function () {
    console.log(2)
})

console.log(3)

输出顺序是:3 2 1

​ 首先是读取了计时器,然后将计时结束后的任务放入到了延时队列中,然后遇到了Promise函数,然后会将相应的任务放入到微队列中,然后执行了console.log(3),然后渲染进程先去读取微队列发现其中有一个任务,所以将会立即执行微队列中的第一个任务,然后继续检测微队列中是否还有其他任务,发现没有任务了,将去读取延时队列中的任务

示例4:

function a() {
    console.log(1)
    Promise.resolve().then(function () {
        console.log(2)
    })
}

setTimeout(function() {
    console.log(3)
    Promise.resolve().then(a)
}, 0)

Promise.resolve().then(function () {
    console.log(4)
})

console.log(5)

输出顺序是:5 4 3 1 2

​ 首先遇到函数a,不执行,然后遇到定时器setTimeout计时线程将会在时间结束后将任务放入到延时队列中,然后遇到Promise函数,在结束后会将回调任务放入到微队列中,然后遇到了console.log(5)将输出5,然后所有的代码执行完,去微队列中读取是否有任务,然后输出4,然后微队列中没有任务了,就去延时队列中读取是否有任务,读取到有任务,随即输出3,然后又遇到了Promise函数,在当前的任务完成后,渲染进程又会先去微队列中读取是否有任务,然后输出了1,然后又有一个任务被放入到了微队列中,然后又先读取微队列中的任务,然后输出2

面试题1: 简述一下 JS 的事件循环

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

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

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

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

面试题2: Js中的计时器能做到精确计时吗,为什么

不行,因为:

  1. 计算机的硬件没有原子钟,无法做到精确计时
  2. 操作系统的计时函数本身就有少量偏差,由于JS 的计时器最终调用的的是操作系统的函数,也就携带了这些偏差
  3. 按照W3C的标准,浏览器实现计时器时,如果嵌套层级超过5层,则会带有4毫秒的最少时间,这样在计时时间少于4毫秒时又带来了偏差
  4. 受事件循环的影响,计时器的回调函数只能在主线程空闲时运行,因此又带来了偏差

单线程是异步产生的原因

事件循环是异步的实现方式

有关浏览器事件循环相关概念及其理解的更多相关文章

  1. ruby - 树顶语法无限循环 - 2

    我脑子里浮现出一些关于一种新编程语言的想法,所以我想我会尝试实现它。一位friend建议我尝试使用Treetop(Rubygem)来创建一个解析器。Treetop的文档很少,我以前从未做过这种事情。我的解析器表现得好像有一个无限循环,但没有堆栈跟踪;事实证明很难追踪到。有人可以指出入门级解析/AST指南的方向吗?我真的需要一些列出规则、常见用法等的东西来使用像Treetop这样的工具。我的语法分析器在GitHub上,以防有人希望帮助我改进它。class{initialize=lambda(name){receiver.name=name}greet=lambda{IO.puts("He

  2. ruby-on-rails - 在 Ruby 中循环遍历多个数组 - 2

    我有多个ActiveRecord子类Item的实例数组,我需要根据最早的事件循环打印。在这种情况下,我需要打印付款和维护日期,如下所示:ItemAmaintenancerequiredin5daysItemBpaymentrequiredin6daysItemApaymentrequiredin7daysItemBmaintenancerequiredin8days我目前有两个查询,用于查找maintenance和payment项目(非排他性查询),并输出如下内容:paymentrequiredin...maintenancerequiredin...有什么方法可以改善上述(丑陋的)代

  3. ruby - RuntimeError(自动加载常量 Apps 多线程时检测到循环依赖 - 2

    我收到这个错误:RuntimeError(自动加载常量Apps时检测到循环依赖当我使用多线程时。下面是我的代码。为什么会这样?我尝试多线程的原因是因为我正在编写一个HTML抓取应用程序。对Nokogiri::HTML(open())的调用是一个同步阻塞调用,需要1秒才能返回,我有100,000多个页面要访问,所以我试图运行多个线程来解决这个问题。有更好的方法吗?classToolsController0)app.website=array.join(',')putsapp.websiteelseapp.website="NONE"endapp.saveapps=Apps.order("

  4. ruby - 在 Ruby 中用键盘诅咒数组浏览 - 2

    我正在尝试在Ruby中制作一个cli应用程序,它接受一个给定的数组,然后将其显示为一个列表,我可以使用箭头键浏览它。我觉得我已经在Ruby中看到一个库已经这样做了,但我记不起它的名字了。我正在尝试对soundcloud2000中的代码进行逆向工程做类似的事情,但他的代码与SoundcloudAPI的使用紧密耦合。我知道cursesgem,我正在考虑更抽象的东西。广告有没有人见过可以做到这一点的库或一些概念证明的Ruby代码可以做到这一点? 最佳答案 我不知道这是否是您正在寻找的,但也许您可以使用我的想法。由于我没有关于您要完成的工作

  5. ruby-on-rails - 相关表上的范围为 "WHERE ... LIKE" - 2

    我正在尝试从Postgresql表(table1)中获取数据,该表由另一个相关表(property)的字段(table2)过滤。在纯SQL中,我会这样编写查询:SELECT*FROMtable1JOINtable2USING(table2_id)WHEREtable2.propertyLIKE'query%'这工作正常:scope:my_scope,->(query){includes(:table2).where("table2.property":query)}但我真正需要的是使用LIKE运算符进行过滤,而不是严格相等。然而,这是行不通的:scope:my_scope,->(que

  6. ruby-on-rails - 浏览 Ruby 源代码 - 2

    我的主要目标是能够完全理解我正在使用的库/gem。我尝试在Github上从头到尾阅读源代码,但这真的很难。我认为更有趣、更温和的踏脚石就是在使用时阅读每个库/gem方法的源代码。例如,我想知道RubyonRails中的redirect_to方法是如何工作的:如何查找redirect_to方法的源代码?我知道在pry中我可以执行类似show-methodmethod的操作,但我如何才能对Rails框架中的方法执行此操作?您对我如何更好地理解Gem及其API有什么建议吗?仅仅阅读源代码似乎真的很难,尤其是对于框架。谢谢! 最佳答案 Ru

  7. ruby-on-rails - 事件管理员日期过滤器日期格式自定义 - 2

    是否有简单的方法来更改默认ISO格式(yyyy-mm-dd)的ActiveAdmin日期过滤器显示格式? 最佳答案 您可以像这样为日期选择器提供额外的选项,而不是覆盖js:=f.input:my_date,as::datepicker,datepicker_options:{dateFormat:"mm/dd/yy"} 关于ruby-on-rails-事件管理员日期过滤器日期格式自定义,我们在StackOverflow上找到一个类似的问题: https://s

  8. CAN协议的学习与理解 - 2

    最近在学习CAN,记录一下,也供大家参考交流。推荐几个我觉得很好的CAN学习,本文也是在看了他们的好文之后做的笔记首先是瑞萨的CAN入门,真的通透;秀!靠这篇我竟然2天理解了CAN协议!实战STM32F4CAN!原文链接:https://blog.csdn.net/XiaoXiaoPengBo/article/details/116206252CAN详解(小白教程)原文链接:https://blog.csdn.net/xwwwj/article/details/105372234一篇易懂的CAN通讯协议指南1一篇易懂的CAN通讯协议指南1-知乎(zhihu.com)视频推荐CAN总线个人知识总

  9. TimeSformer:抛弃CNN的Transformer视频理解框架 - 2

    Transformers开始在视频识别领域的“猪突猛进”,各种改进和魔改层出不穷。由此作者将开启VideoTransformer系列的讲解,本篇主要介绍了FBAI团队的TimeSformer,这也是第一篇使用纯Transformer结构在视频识别上的文章。如果觉得有用,就请点赞、收藏、关注!paper:https://arxiv.org/abs/2102.05095code(offical):https://github.com/facebookresearch/TimeSformeraccept:ICML2021author:FacebookAI一、前言Transformers(VIT)在图

  10. ruby-on-rails - 事件记录 : Select max of limit - 2

    我正在尝试将以下SQL查询转换为ActiveRecord,它正在融化我的大脑。deletefromtablewhereid有什么想法吗?我想做的是限制表中的行数。所以,我想删除少于最近10个条目的所有内容。编辑:通过结合以下几个答案找到了解决方案。Temperature.where('id这给我留下了最新的10个条目。 最佳答案 从您的SQL来看,您似乎想要从表中删除前10条记录。我相信到目前为止的大多数答案都会如此。这里有两个额外的选择:基于MurifoX的版本:Table.where(:id=>Table.order(:id).

随机推荐