前言
晚期浏览器的页面是运行在 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,然而实在的状况是这样的嘛。
因为本篇文章次要内容为事件循环,这里就间接说出答案:并不是!!!
起因将在之后独自开一篇文章解释,敬请关注!