前言
众所周知, JavaScript是单线程这一核心,可是浏览器又能很好的处理异步请求,那么到底是为什么呢?其中的原理与事件循环机制大有关系。
在探索事件循环之前,我们得先了解浏览器执行线程~~
浏览器的渲染进程是多线程的,浏览器每一个tab标签都代表一个独立的进程,其中浏览器内核属于浏览器多进程中的一种,主要负责页面渲染,脚本执行,事件处理等。其包含的线程有以下几种
GUI 渲染线程:负责渲染页面,解析 HTML,CSS 构成 DOM 树;
JS 引擎线程:解释执行代码、用户输入和网络请求;
事件处理线程:click、mouse等交互事件发生后将事件函数放入队列;
定时器触发线程:等时间结束后执行函数推入任务队列中;
http网络请求线程:处理用户的get、post请求,返回结果推入任务队列中。
了解了浏览器渲染流程,还要了解JS的运行机制。JS的运行机制就是事件循环
执行栈
JS运行的环境称之为宿主环境。
执行栈:call stack,一个数据结构,用于存放各种函数的执行环境,每一个函数执行之前,它的相关信息会加入到执行栈。函数调用之前,创建执行环境,然后加入到执行栈;函数调用之后,销毁执行环境。
事件循环
js中所有的任务可以分为同步任务和异步任务,同步任务是立即执行的任务,同步任务一般会直接进入到主线程中执行;而异步任务,就是异步执行的任务,比如ajax网络请求,setTimeout 定时函数等都属于异步任务,异步任务会通过任务队列(先进先出)的机制来进行协调。同步和异步任务分别进入不同的执行环境,同步的进入主线程,即主执行栈,异步的进入任务队列。主线程内的任务执行完毕为空,会去任务队列读取对应的任务,推入主线程执行。 这种不断重复就是我们说的 Event Loop (事件循环)。具体流程如下图。
在事件循环中,每进行一次循环操作称为tick,每一次 tick 的任务键的步骤可以总结如下:1.执行一个宏任务(栈中没有就从事件队列中获取);2.执行过程中如果遇到微任务,就将它添加到微任务的任务队列中;3.宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行);4.当前宏任务执行完毕,开始检查渲染,然后GUI线程接管渲染;5.渲染完毕后,JS线程继续接管,开始下一个宏任务(从事件队列中获取)
宏任务主要包含:script( 整体代码)、setTimeout、setInterval、I/O、UI 交互事件、setImmediate(Node.js 环境)
微任务主要包含:Promise、MutaionObserver、process.nextTick(Node.js 环境)
事件循环例子
console.log('script start');
//整体 script 作为第一个宏任务进入主线程,遇到 console.log,输出 script start
setTimeout(function() {
console.log('setTimeout');
}, 0);
//遇到 setTimeout,其回调函数被分发到宏任务 Event Queue 中
Promise.resolve().then(function() {
console.log('promise1');
}).then(function() {
console.log('promise2');
});
//遇到 Promise,其 then函数被分到到微任务 Event Queue 中,记为 then1,之后又遇到了 then 函数,将其分到微任务 Event Queue 中,记为 then2
console.log('script end');
//遇到 console.log,输出 script end
如此,Event Queue(事件队列)中存在三个任务:宏任务:setTimeout 微任务:then1、then2。执行微任务首先then1,输出 promise1, 然后执行 then2,输出 promise2,这样就清空了所有微任务
执行 setTimeout 任务,输出 setTimeout 至此,输出的顺序是:script start, script end, promise1, promise2, setTimeout
总结:
JavaScript 是一门单线程语言,异步操作都是放到事件循环队列里面,等待主执行栈来执行的,并没有专门的异步执行线程。
到此这篇关于js的事件循环机制的文章就介绍到这了,更多相关js事件循环内容请搜索极客世界以前的文章或继续浏览下面的相关文章希望大家以后多多支持极客世界! |
请发表评论