惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Security Latest
Security Latest
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Stack Overflow Blog
Stack Overflow Blog
WordPress大学
WordPress大学
N
Netflix TechBlog - Medium
GbyAI
GbyAI
云风的 BLOG
云风的 BLOG
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
宝玉的分享
宝玉的分享
博客园 - 【当耐特】
C
Cyber Attacks, Cyber Crime and Cyber Security
雷峰网
雷峰网
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
T
Threat Research - Cisco Blogs
NISL@THU
NISL@THU
Spread Privacy
Spread Privacy
P
Proofpoint News Feed
J
Java Code Geeks
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
MyScale Blog
MyScale Blog
T
Tor Project blog
P
Proofpoint News Feed
C
CERT Recently Published Vulnerability Notes
P
Privacy & Cybersecurity Law Blog
MongoDB | Blog
MongoDB | Blog
Simon Willison's Weblog
Simon Willison's Weblog
C
Cybersecurity and Infrastructure Security Agency CISA
L
LINUX DO - 热门话题
小众软件
小众软件
G
GRAHAM CLULEY
P
Privacy International News Feed
AWS News Blog
AWS News Blog
Know Your Adversary
Know Your Adversary
P
Palo Alto Networks Blog
人人都是产品经理
人人都是产品经理
S
Schneier on Security
Scott Helme
Scott Helme
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
B
Blog RSS Feed
T
The Exploit Database - CXSecurity.com
Recent Announcements
Recent Announcements
E
Exploit-DB.com RSS Feed
C
CXSECURITY Database RSS Feed - CXSecurity.com
U
Unit 42
The Register - Security
The Register - Security
S
Securelist
Martin Fowler
Martin Fowler
Project Zero
Project Zero
大猫的无限游戏
大猫的无限游戏
Cisco Talos Blog
Cisco Talos Blog

博客园 - 爱喝可乐的咖啡

引入AI辅助的3D游戏美术工作流 基于ThreeJs的大屏3D地图(二)——气泡图、渐变柱体与热力图 基于Three.js的大屏3D地图(一)——地图模型渲染 不止于面向对象的SOLID原则 沿SVG路径的颜色渐变 参照DefenseGrid在Unity中实现合理的塔防寻路机制 slate源码解析(三)- 定位 定位解析一个因脚本劫持导致webpack动态加载异常的问题 slate源码解析(二)- 基本框架与数据模型 slate源码解析(一)- 序言 重新捋一捋React源码之更新渲染流程 从源码入手探究一个因useImperativeHandle引起的Bug [译]深入了解现代web浏览器(四) [译]深入了解现代web浏览器(三) [译]深入了解现代web浏览器(二) [译]深入了解现代web浏览器(一) 记一次flex布局中子项目尺寸不受flex-shrink限制的问题 浅析富文本编辑器框架Slate.js 关于在异步操作中访问React事件对象的问题
依据HTML标准再探Javascript事件循环及其与浏览器渲染的关系
爱喝可乐的咖啡 · 2022-06-17 · via 博客园 - 爱喝可乐的咖啡

Javascript的一些基础概念

JavaScript执行引擎在宿主环境中是单线程的,这意味着在同一时间内只能执行一个任务。在Javascript运行期间,引擎会创建和维护相应的堆(heap)和栈(stack)这两个数据结构;堆是存放数据变量的地方(这里很多前端er有个误区,认为js的引用类型存放在堆中,基础类型的变量是存放在栈中的),栈就是指执行栈。

对于浏览器来说,当它第一次加载你的<script>标签时,它默认进入了全局执行环境;如果你在全局代码中调用了一个函数,执行流就会进入到这个函数当中,创建一个新的执行环境并且把这个环境添加到执行栈的顶部。如果你在当前的函数中又调用了其他函数,同样的步骤会再来一次。浏览器始终执行当前在栈顶部的执行环境。一旦函数完成了当前的执行环境,它就会被弹出栈的顶部, 把控制权返回给栈中的下个执行环境:

上述任务都是同步执行的,这就会导致一个很常见的问题:如果执行了一段非常耗时的同步代码 例如请求数据、上传文件等,浏览器就会长时间无法渲染,页面阻塞不能响应交互。对于需要处理这类IO密集型任务的浏览器来说,Javascript当然是需要异步非阻塞机制的,这就是下面要讲的事件循环机制。


Event Loop

上面讲了执行栈中的所有任务从顶向下同步执行;但当遇到一些需要异步执行的任务,如ajaxsetTimeout等时,会立即返回函数,然后将异步操作交给浏览器内核中的其他模块处理(如timer、network、DOM Binding模块等),接着主线程继续往下执行栈中的任务。当这些异步操作完成后如ajax接受完响应、setTimeout到达指定延时;这些任务的回调就会被放入到任务队列中。不同的异步任务的回调函数会被放入不同的任务队列之中。

在浏览器中是有两种不同类型的异步任务,国内的文章好像都是叫做“宏任务”和“微任务”,但在HTML标准(没错,事件循环这玩意是在HTML标准里的而不是ECMAScript标准定的)中并没有这么区分。在标准中,我们常说的“宏任务”就是指Task,常见的来源(Task Source)有:

  • DOM操作
  • UI交互
  • 网络请求
  • History APIs
  • 定时器任务
  • 。。。。。。

不同Task Source所产生的Task还会有自己的队列(Task Queue);当多个Task Queue都存在Task时,浏览器会自行调度决定先执行哪个;但同一Task Queue里的Task一定是按先进先出(FIFO)的顺序执行的。
后面出的新特性如Promise和MutationObserver,通常就是标准中的'Microtask'。与Task Queue不同的是,Microtask Queue只有一个。

当执行栈中所有任务都执行完了后,就会去Event Loop中去取出任务,放到执行栈中同步执行。Event Loop做了哪些事情呢,参考标准(Processing model),大概总结如下:

  1. 从Task Queue取出一个Task执行;至于如何选择哪个Task Queue(前面说过,Event Loop中会有多个Task Queue)取决于浏览器实现方。

  2. 依次执行Microtask Queue中的所有Microtask。注意,在本轮循环中新增的Microtask也会在此执行完

  3. 更新渲染

    • 浏览器会根据刷新频率和页面状态等因素来决定是否要跳过该次渲染更新。例如 当浏览器试图达到60Hz的刷新频率时,会让更新渲染的次数在一秒内最多达到60次(16.6ms一次),但如果浏览器发现页面无法稳定维持该帧率的话,就会降到30Hz,那么更新渲染的几率就被降低了一半;或者当前页面的可见性为否时,浏览器可以将该页面降低至每秒4次甚至更低的更新渲染次数;又或者当前的渲染不会有可见的差异等。

    • 如果确认需要更新渲染:

      1. 触发resizescrollfullscreen等事件的处理函数并传入刚刚前面设置的now作为时间戳;并不是说到这里才会更新视图,窗口大小和滚动是会马上更新的,只是需要在一次事件循环中走到这一步时,才会触发这些事件的分发

      2. 执行帧动画回调,传入刚刚前面设置的now作为时间戳;window.requestAnimationFrame就是在此时执行的(回调函数中传入的是上一帧渲染结束的时间)

      3. 执行Intersection Observer API的回调

      4. 渲染页面内容并提交

  4. 每当一轮循环结束后,会判断Task Queue和Microtask Queue是否为空。都为空的话则再根据空闲周期算法决定是否执行requestIdleCallback回调。