WordPress建影视站,wordpress下载管理员,机电建设工程施工网站图片,合肥外贸网站推广JavaScript中的异步代码
JavaScript是一个单线程非阻塞的脚本语言。这代表代码是执行在一个主线程上面的。但是JavaScript中有很多耗时的异步操作#xff0c;例如AJAX#xff0c;setTimeout等等#xff1b;也有很多事件#xff0c;例如用户触发的点击事件#xff0c;鼠标…JavaScript中的异步代码
JavaScript是一个单线程非阻塞的脚本语言。这代表代码是执行在一个主线程上面的。但是JavaScript中有很多耗时的异步操作例如AJAXsetTimeout等等也有很多事件例如用户触发的点击事件鼠标事件等等。这些异步操作并不会阻塞我们代码的执行。例如
let a 1;
setTimeout(() {console.log(-, a)
}, 10);
a 2;
// 输出 - 2可以看到上述代码在浏览器中执行时遇到setTimeout操作并没有阻塞等待异步操作的结束再继续执行代码而是先继续执行后面的代码。等异步操作结束后浏览器再回来执行异步回调中的代码。因此上述代码的console.log输出时a的值已经变为了2。
这些异步非阻塞的实现就是靠Javascript中的事件循环机制。
JavaScript中的线程
上面说到JavaScript是一个单线程的语言这句话并不完全对。单线程指的是代码在一个主线程中运行但是代码所触发的任务不一定在主线程运行。除了执行代码的线程之外执行JavaScript的环境中还包含其他很多线程。其中浏览器的线程与Node.js中的线程也不相同。
浏览器中的线程
注意这里对于浏览器线程进行了抽象和总结。实际上浏览器的线程和进程要更复杂而且有时候会根据浏览器版本的不同而变化因此仅供参考。
JS主线程 负责运行JavaScript代码解析HTMLCSS构建DOM树布局和绘制页面等等。事件监听线程 负责监听触发的各种事件放入事件循环中。HTTP请求线程 负责处理各类网络请求。定时触发器线程 为setIntervalsetTimeout定时触发操作等操作进行定时计数的线程。
浏览器中的进程
上面的线程实际上都在浏览器中的渲染进程中包含。一个浏览器要想正常运行只做上述的操作是不够的。我们以Chrome为例列举一个浏览器运行所需要的进程。
浏览器进程 负责网页外的界面功能例如地址栏书签等等。GPU进程 负责使用GPU渲染界面。网络进程 负责网络相关的请求处理。插件进程 负责浏览器插件运行。渲染进程 负责网页内页面展示相关的操作即上一节浏览器中的线程包含的所有线程都在这个进程中执行。
一个浏览器可以拥有多个标签页在不同的标签页中除了渲染进行之外都是共享的。即我们打开一个新的标签页时会产生一个新的渲染进程。当在原标签页中打开新标签页且属于同一个域则共享一个渲染进程
进程与线程的关系
上面我们了解了浏览器中的进程和线程有些同学就会有疑问为什么要设立这么多的进程和线程 进程是操作系统分配资源的基本单位而线程是CPU任务调度和执行的基本单位。 简单理解下就是一个完整的应用程序是以进程为单位的即至少有一个进程。而一段程序/代码在CPU的独立执行则至少以线程为单位。不同的进程和不同的线程都可以并行运行。
一个进程可以包含很多个线程多个线程共享一个进程的资源比如内存。当一个进程崩溃后不会影响其他进程但是当一个线程崩溃它所在的整个进程都会崩溃掉这个进程内的其他线程也会崩溃。
因此为了同时并行执行代码和异步请求浏览器中的渲染进程包含很多线程来并行运行任务。而为了让不同标签页的网页不互相影响不同标签页拥有独立的渲染进程。这样即使某个网页崩溃也不会影响其他标签页。
Node.js中的线程
JS主线程 负责运行JavaScript代码。libuv的异步I/O线程池 负责实现事件循环和异步IO等操作在不同操作系统的具体实现方式不同。用户创建的线程
上述这些进程和线程的说明也仅仅是进行了抽象和简化事实上浏览器和Node.js中的进程和线程数要更多处理也更复杂。
宏任务与微任务
Javascript中的异步任务大致可以分为两种宏任务和微任务。宏任务和微任务的执行顺序和优先级是不同的具体的执行顺序问题我们在事件循环中描述这里先来看一下哪些操作属于宏任务哪些属于微任务。这里仅仅是简单介绍更详细的要在了解事件循环之后说明。
宏任务
任务浏览器Node.js描述setTimeout✓✓在指定的毫秒数后调用函数setInterval✓✓定时调用函数script标签✓整体代码块I/O请求✓✓例如文件请求网络请求等DOM事件✓例如点击事件hover事件等requestAnimationFrame✓浏览器重绘前更新动画postMessage✓iframe跨域通信MessageChannel✓✓管道通信setImmediate✓一次事件循环执行完毕调用
微任务
任务浏览器Node.js描述Promise中resolve和reject回调✓✓async函数中的await异步函数✓✓MutationObserver✓监听DOM变动触发process.nextTick✓当前任务结束后执行
事件循环
与上面进程与线程的介绍一样在浏览器中与Node.js中实现循环的方式也并不相同。下面我们来分别简单介绍一下。注意这仅仅是对执行逻辑的抽象和总结实际上浏览器和Node.js中的实现要更复杂。
浏览器中的事件循环
浏览器中的事件循环可以分为两个队列宏任务队列和微任务队列。具体的任务执行顺序如下
解析HTML中遇到script标签开始执行第一个宏任务。在宏任务执行中遇到宏任务执行其中的请求例如网络请求定时器在请求完成后将回调放入宏任务队列中。在宏任务执行中遇到微任务暂不执行回调而是放入微任务队列中。宏任务执行完成。开始依次执行微任务队列中的任务。微任务执行中遇到宏任务或者微任务处理方式同上分别放入各自的队列中。微任务队列清空后开始执行宏任务队列中的下一个任务。
在事件循环的流程中微任务的优先级实际上更高执行完一个宏任务之后要执行微任务队列中的所有任务。
为什么要区分宏任务和宏任务,优先级也不同
因为不同任务的开销不同有的任务需要调用不同的线程甚至进程有的任务需要等待请求返回甚至定时。
如果将全部的任务同步执行那些耗时较久的任务会阻塞造成整个页面加载缓慢。假设有请求A耗时10秒请求B耗时20秒如果同步执行需要耗费30秒。如果将请求由其它线程实现回调放入宏任务则执行流程变为执行代码-碰到A请求,其他线程异步等待返回-继续执行代码-碰到b请求,其他线程异步等待返回。A和B就实现了异步请求回调被分别放入宏任务等待下次事件循环。耗时间为20秒。为什么微任务的优先级更高因为微任务大部分是耗时不太久不需要等待其他线程/进程等待完成通知的。因此微任务相当于在宏任务的基础上进行了“插队”拥有更高的优先级也提高了页面的响应速度。
为什么script标签是宏任务呢
script标签可能需要异步请求获取例如script srcmyscripts.js/script。script标签是嵌入在HTML中的浏览器需要将HTML中的script标签解析出来供执行这个步骤需要耗费一定的时间。
浏览器事件循环的更多说明
WHATWG网页超文本应用技术工作小组在官网对事件循环和任务队列做出了更详细的说明和解释可以作为参考说明文档。在新的说明中任务的分类和事件循环已经有了部分区别这里简要说一下更多还请直接查看文档
事件循环不一定对应于多线程。例如多个事件循环可以在单个线程中协作调度。任务队列并不是一个严格的队列而是一个集合。每次从队列中取出一个可以被执行的任务而不是选取第一个任务可能该任务还在阻塞中。宏任务队列有多个不同类型的任务(任务源)放置在不同的任务队列中。具体的选取规则浏览器根据实际情况确定。
Node.js中的宏任务队列
Node.js的官网给出了事件循环的文档。它的事件循环要比浏览器的看起来复杂一些。下面是Node.js的宏任务队列。 ┌───────────────────────────┐
┌─│ timers │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ pending callbacks │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ idle, prepare │
│ └─────────────┬─────────────┘ ┌───────────────┐
│ ┌─────────────┴─────────────┐ │ incoming: │
│ │ poll │─────┤ connections, │
│ └─────────────┬─────────────┘ │ data, etc. │
│ ┌─────────────┴─────────────┐ └───────────────┘
│ │ check │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
└──┤ close callbacks │└───────────────────────────┘Node.js的宏任务队列并不是一整个队列而是根据事件类型做出了区分分为了六个队列依次执行
timers 定时器队列执行定时器的回调pending callbacks 挂起的回调函数用于某些系统回调idle, prepare 仅在内部使用poll 执行I/O事件回调check setImmediate回调close callbacks close事件的回调例如 socket.on(close, ...)
其中我们的大部分宏任务回调都会在poll阶段执行除了timers、check和close callbacks阶段的特殊回调。每个宏任务队列都有自己的微任务队列。 Node.js事件循环的流程
首先执行主线代码遇到宏任务就分配到对应的宏任务队列中微任务也划分到主线的微任务队列中直到执行完毕。执行主线代码的微任务队列中的所有任务。没有宏任务则执行结束有则开始事件循环。在事件循环中按照上述的6个宏任务队列依次执行。下面的步骤是单个队列中的流程。在单个宏任务队列中选择一个宏任务执行。如果执行中遇到新的宏任务就分配到对应的宏任务队列中。遇到微任务就放到该宏任务的微任务队列中。一个宏任务执行完毕后执行process.nextTick中的回调如果有。执行当前宏任务的微任务队列中的任务直到微任务队列清空。在上面的单个宏任务队列中再选择一个宏任务执行。直到当前宏任务队列清空或者到达上限。选择下一个宏任务队列执行。
6个宏任务队列都执行完毕才叫做一次事件循环执行完毕。
Node.js的11版本之前的区别
其中在Node.js的11版本之前宏任务和微任务的执行关系与上述流程不同
每个宏任务队列有一个微任务队列。在单个宏任务队列中首先执行完所有的宏任务如果遇到微任务就放到微任务队列中。当单个宏任务队列中的所有宏任务执行完毕后再执行该宏任务队列的微任务队列。
对比执行流程的区别可以看到Node.js的11版本提高了微任务队列中的优先级让Node.js中微任务队列的优先级和浏览器中的表现类似。而process.nextTick可以看做是一个比微任务更高优先级的钩子。
注意
setTimeout的时间即使设置为0也会有一个最小时间因此它与setImmediate谁更早执行不一定。并不是所有回调函数都是异步的。例如new Promise(fun)中的回调是同步执行在回调中遇到resolve(), reject()等才是微任务异步执行的。
参考
JavaScript 之事件循环 (Event Loop) https://xie.infoq.cn/article/921841837025748baac847030The Node.js Event Loop, Timers, and process.nextTick() https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick https://nodejs.org/zh-cn/docs/guides/event-loop-timers-and-nexttick深入理解浏览器中的进程与线程 https://juejin.cn/post/6991849728493256741这一篇浏览器事件循环可能会颠覆部分人的对宏任务和微任务的理解 https://juejin.cn/post/7259927532249710653HTML Living Standard (event-loops) https://html.spec.whatwg.org/multipage/webappapis.html#event-loops阿里一面熟悉事件循环那谈谈为什么会分为宏任务和微任务 https://juejin.cn/post/7073099307510923295node.js事件循环简单理解——定时器process.nextTick()等 https://blog.csdn.net/qq_46561394/article/details/123172336手摸手带你彻底掌握任务队列、事件循环、宏任务、微任务 https://juejin.cn/post/6979876135182008357浏览器UI线程和JS线程是同一个线程吗 https://www.zhihu.com/question/264253488微信小程序的双线程设计有何创新之处浏览器的渲染线程和 JS 线程本来不就是两个独立线程吗 https://www.zhihu.com/question/446103629