简述
- JavaScript引擎是单线程运行的,浏览器无论在什么时候都有且只有一个线程在运行JavaScript程序,JavaScript本身不可以异步
- 可执行异步操作的原因: JS的宿主环境(eg: 浏览器, Node)是多线程的,宿主环境通过某种方式(事件驱动)使得JS具有异步的属性
JS触发异步原理
1. JS引入异步的原因
JS是单线程语言,浏览器只分配给JS一个主线程,用来执行任务(函数), 但一次只能执行一个任务,这些任务形成一个任务队列排队等候执行。但是前端的很多任务都是非常耗时的,比如http请求、定时器、事件监听等。如果让他们都按照同步原理顺序执行,那么执行效率会非常低,甚至导致页面的假死。
2. 浏览器模型
浏览器模型- 用户界面(User Interface): 包括地址栏、前进/后退按钮、书签菜单等
- 浏览器引擎 (Browser engine):在用户界面和呈现引擎之间传送指令
- 呈现引擎 (Rendering engine):又称渲染引擎,也被称为浏览器内核,在线程方面又称为UI线程
- 网络 (Networking):用于网络调用,比如 HTTP 请求
- 用户界面后端 :用于绘制基本的窗口小部件,UI线程和JS共用一个线程
- JavaScript解释器 (Javascript Interpreter):用于解析和执行 JavaScript 代码
- 数据存储 (Data Persistence):这是持久层。浏览器需要在硬盘上保存各种数据,例如 Cookie
3. 浏览器对JS异步的支持
浏览器内核允许多个线程异步执行,这些线程在内核控制下相互配合以保持同步。
三个常驻线程:
a). JavaScript引擎线程
b). 页面渲染线程
c). 浏览器事件触发线程
执行完就终止的线程
a). HTTP请求线程等
b). 定时触发器线程
4. 任务队列
顾名思义: 排着任务的队列。所谓任务就是webAPIs(浏览器为异步任务单独开辟的线程统一称为webAPIs)返回的一个个通知,让JS主线程在读取任务队列的时候得知这个异步任务已经完成,下一步该执行这个任务的回调函数了。主线程拥有多个任务队列,不同的任务队列用来排列来自不同任务源的任务。
- 任务源: 像setTimeout / Promise / DOM 事件等都是任务源。来自同类任务源的任务我们称它是同源的, 比如setTimeout和setInterval就是同源的。
5. 事件循环(event loop)
事件循环(event loop)由三大模块组成: 函数调用栈、webAPIs、任务队列
执行过程:
1.主线程在遇到ajax或者setTimeout 这种异步操作时会交给浏览器的webAPIs,然后继续执行后面的代码,直到执行栈为空。
- 浏览器会在不确定的时间将完成的任务返回,排到相应的任务队列后。
- 执行栈为空时,主线程会到任务队列中去取任务,这些任务会告诉下一步应该执行那些回调函数。任务队列是具有优先级的,按照优先级顺序决定访问的先后循序。而优先级在不同的环境中又有所不同,不能一概而论。
- 每访问一个队列,执行栈会执行完这个任务队列的的所有代码,然后再取下一个任务队列需要执行的任务代码。如果在执行中遇到了属于当前任务的异步队列时,此次任务的返回不会排到当前的任务队列之后,因为这属于两次单独的事件循环,会被区分开来。
就这样循环执行,直到三大块全为空,称为事件循环。
6. AJAX请求是否异步
ajax请求内容的时候是异步的,当请求完成后,会触发请求完成的事件,然后把回调函数放到任务队列中,等到主线程执行该回调函数时还是单线程的。
7. 界面渲染线程是单独开辟的线程,是不是DOM一变化,界面就立刻重新渲染?
答案: 不是。
如果DOM一变化界面就重新渲染,效率必然很低,所以浏览器的机制规定界面渲染线程和主线程是互斥的,主线程执行任务时,浏览器渲染线程处于挂起状态。
网友评论