前言
晚期浏览器的页面是运行在 UI线程
上的,为了在页面中引入JavaScript,不便JS操作DOM,JS也须要运行在和页面雷同的线程中,所以JS是单线程的。
一、基本概念
事件循环系统由主线程、调用栈、宏工作、微工作、音讯队列等形成。
主线程:UI线程
调用栈:一种数据结构、用来治理在主线程上执行的函数的调用关系
音讯队列:用于寄存期待主线程执行的宏工作 ‘事件’列表
工作:UI线程每次从音讯队列中取出事件、执行事件的过程称为一次工作
宏工作:音讯队列中期待被主线程执行的事件、宏工作蕴含鼠标、键盘、触控板事件
微工作:一个须要异步执行的函数、执行的机会在主线程执行完结之后、以后宏工作完结之前
常见宏工作有:主js、UI渲染、setTimeout、setInterval、setImmediately、requestAnimationFrame、I/O等
常见微工作有:process.nextTick()、promise.then()(new Promise不算!)、Object.observe()等
二、事件循环
生命周期
=> 初始化
1、从音讯队列中取出工作2、全局执行上下文压入调用栈中3、在全局上下文中创立微工作队列
=> 执行工作
事件执行中:1、减少新的上下文到调用栈中2、执行函数中的代码3、封装新的事件并增加到音讯队列中事件执行后:1、在函数执行完结后将以后函数的执行上下文从调用栈中弹出2、查问并执行全局上下文中的微工作队列3、垃圾回收?
=> 完结以后事件
从音讯队列中取出新的事件开始执行、周而复始
图文解说
备注:本文章局部图片来自 《极客工夫:图解 Google V8》,如有侵权,请告知删除,感激!
用以下代码为例,了解事件循环机制:
function foo() { console.log('setTimeout star'); out && out(); console.log('Promise star'); pro && pro(); console.log('bar star'); bar && bar();}function bar() { console.log('bar');}function pro() { Promise.resolve().then(res => { console.log('Promise'); });}function out() { setTimeout(function() { console.log('setTimeout:'); }, 100);}foo();
从音讯队列中取出foo()
事件
创立调用栈并将全局执行函数上下文压入栈中
:将foo函数执行上下文压入栈中
:执行console.log('setTimeout star');
:将out
函数执行上下文压入栈中
:将setTimeout的回调函数
封装成一个新的事件100ms后增加到音讯队列中
:out
函数执行完结、将out函数上下文从调用栈弹出
:执行console.log('Promise star');
:将pro
函数执行上下文压入栈中
:将微工作
增加到调用栈中全局执行上下文的微工作队列
中
:pro
函数执行完结、将pro函数上下文从调用栈弹出
:执行console.log('bar star');
:将bar
函数执行上下文压入栈中
:执行console.log('bar');
:bar
函数执行完结、将bar函数上下文从调用栈弹出
从全局执行函数上下文中将微工作队列
中得函数取出,执行console.log('Promise');
完结以后事件、当从setTimeout事件
增加到音讯队列后取出事件,执行console.log('setTimeout');
三、常见关联问题
堆栈溢出
function foo() { foo();}foo()
上述代码中foo函数中调用了本身,依据下面咱们理解的事件循环机制,咱们会发现在执行foo函数的过程中,主线程会一直的向调用栈中压入foo函数的执行上下文,而因为foo函数始终没有执行完结,所以函数上下文并不会从调用栈中移除,然而调用栈的容量是无限的,所以就会造成调用栈的溢出。
面对此类问题,咱们有两种解决方案:
1、宏工作异步调用
function foo() { setTimeout(function() { foo(); }, 0);}// 这里间接给出代码,能够根据上述内容分析为什么这样就不会造成堆栈溢出(我是不会抵赖本人懒得写了~)// 另外须要留神,即使这样能够不造成堆栈溢出,但咱们仍旧不举荐这样写,大量的事件阻塞了音讯队列中其余事件的执行
2、按条件完结调用——递归函数
宏工作和微工作的执行程序
将下面案例的代码复制到浏览器中执行,咱们会发现foo();
执行的后果是:
// 打印后果:// setTimeout star// Promise star// bar star// bar// Promise// setTimeout
这里咱们留神到一个问题,那便是尽管在代码中三个函数的执行程序是setTimeout、Promise、bar,然而因为浏览器对他们的解决形式不统一,导致最终的执行后果是bar、Promise、setTimeout,这里其实就是同步与异步、微工作与宏工作的代码执行程序问题,依据下面代码的后果与咱们对这段代码的执行过程剖析能够得出以下论断:
- 先同步、后异步
- 微工作在主线程执行事件完结之后、以后宏工作完结之前执行
- 依据第二条可得、当一个事件中如果同时存在微工作和宏工作、微工作在宏工作之前执行。
setTimeout、setInterval等定时器函数的执行工夫距离
这里咱们先想一个问题,下面代码中setTimeout得提早设定为100ms,则实践上打印setTimeout star和setTimeout之间的工夫距离也应该为100ms,然而实在的状况是这样的嘛。
因为本篇文章次要内容为事件循环,这里就间接说出答案:并不是!!!
起因将在之后独自开一篇文章解释,敬请关注!