相信不少人都看过Philip Roberts在JS-Conf上关于Event Loop的演讲.promise
这篇文章就是用来记录观后感.浏览器
Call Stackbash
调用堆栈: 它是一个用于记录函数调用的数据结构(后进先出)。 当咱们调用一个函数时候, 就会将其推入到堆栈中, 当一个函数返回时候, 就会将其推出堆栈的顶部. 另外注意同步代码才会按照顺序立刻进入Call Stack. "异步代码" 后面会讲.数据结构
若是堆栈长时间被占用或者堵塞就会致使咱们常说的blocking script.异步
看下面这段代码: 咱们理解Call Stack的执行过程.函数
function foo (b) {
var a = 5
return a * b + 10
}
function bar (x) {
var y = 3
return foo(x * y)
}
console.log(bar(6)) // 100
复制代码
// 执行过程以下
1. 首先找到即将开始执行的main函数
2. 从console.log(bar(6)) 开始执行代码, 它被推到调用栈的底部.
3. 而后bar()被推到console.log(bar(6))的顶部
4. 以后foo()被推到bar()的顶部, 可是当它执行后当即返回, 被推出堆栈.
5. 接着将bar()弹出
6. 最后在console.log()被弹出,把返回值100打印在输出台.
复制代码
如下图一个错误堆栈为例可能更明了:oop
有时候咱们把一个函数递归调用屡次, 会进入一个死循环. 而对于Chrome浏览器来讲, 栈的大小是有限制的(16000桢), 因而它会抛出一个Max Stack错误.测试
Heapui
堆: 对象是分配在堆里面. 说到堆内存就须要了解栈内存, 引用类型,基本类型.spa
引用类型,值大小不固定,栈内存中存放地址指向堆内存中的对象。是按引用访问的。以下图所示:栈内存中存放的只是该对象的访问地址,在堆内存中为这个值分配空间。因为这种值的大小不固定,所以不能把它们保存到栈内存中。但内存地址大小的固定的,所以能够将内存地址保存在栈内存中。 这样,当查询引用类型的变量时, 先从栈中读取内存地址, 而后再经过地址找到堆中的值。对于这种,咱们把它叫作按引用访问。
Quene
队列: JS运行时候包含一个消息队列, 它是要处理的消息列表(事件)和待执行的回调函数.
Quene是一个先进先出的数据结构,排在前面的事件,优先被主线程读取。主线程的读取过程基本上是自动的,只要执行栈(Call Stack)一清空,Quene上第一位的事件就自动进入主线程。可是,因为存在"定时器"功能,主线程首先要检查一下执行时间,某些事件只有到了规定的时间,才能进入到事件队列,读取事件,执行事件相应的回调.
基本上来讲,这些事件是响应外部异步事件而排队的(例如鼠标被点击或接收到对HTTP请求的响应,固然若是一个事件没有回调, 那么事件是不会进入Quene排队的.
这里我以为重点是理解什么样的代码才会进入到Quene, 我我的喜欢称呼"异步代码". 咱们都知道JS是单线程语音, 若是都采用同步不支持异步的话, 你能够想象加载资源时候堆栈会堵塞成什么样子. 因此JS支持异步回调. 也许有人会问为何异步代码不能够直接进入Call Stack, 其实想一想既然是异步代码那么进入到Call Stack的顺序就是随机插入的显然不合理.
// 到了时间才会推入到队列
console.log(1)
setTimeout(() => {
console.log(2)
}, 200)
setTimeout(() => {
console.log(3)
}, 0)
console.log(4)
// 1 4 3 2
复制代码
浏览器端异步事件: DOM事件,http请求,setTimeout等异步事件。
事件循环的基本工做是查看堆栈和任务队列,当队列看到堆栈为空时,将队列中的第一件event推送到堆栈。在处理任何其余消息以前,会先处理当前事件的回调。
关于队列: 其实分为macro-task/micro-task.
// 测试代码
function execOrder () {
setTimeout(() => console.log('timeout'), 0)
let promise = new Promise((resolve, reject) => {
console.log('Promise')
resolve()
})
promise
.then(() => {
console.log('resolved')
})
console.log('hi')
}
// 执行顺序: Promise hi resolved timeout
复制代码