# EventLoop小计
# 前言
最近在看执行相关的内容,看到了EventLoop 相关的内容,在此做个总结。
# 是什么
事件循环。JavaScript是单线程的,意味着任务需要排队执行。如果前面耗时比较长的任务,会阻塞后面代码的执行,但对于IO(接口请求),后面的任务没必要等着响应回来之后才执行。对于那些一步一步执行,上一步执行完才执行下一步的,称为同步任务。不进入主线程,而进入任务队列(task quene)的任务,称为异步任务。EventLoop就是避免JavaScript在执行过程中出现阻塞的机制。
TIP
JavaScript单线程原因,常见解释为,若有两个线程(A,B)同一时间在操作同一个DOM(D)元素,A线程在往D元素上添加内容(文本、属性或其他内容),但是B线程在删除D元素。
# 相关内容
# 微任务(microtasks)和宏任务(tasks)
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
Promise.resolve().then(function() {
console.log('promise1');
}).then(function() {
console.log('promise2');
});
console.log('script end');
宏任务
script(整体任务)、setTimeout、setInterval、requestAnimationFrame、setImmediate(node)、UI renderding
requestAnimationFrame 在页面重绘前,当前微任务执行后
微任务
Promise then/catch/finally、MutationObserver/process.nextTick(node)
浏览器会在一个宏任务结束后,检查是否有微任务开始执行,requestAnimationFrame,在此执行,然后进行重新渲染,这个过程是循环执行的,这个过程(或者机制)就被成为 EventLoop。
下一次宏任务,必须要等到当前微任务执行完毕,才可以执行。
因此上面的任务的执行结果就位 script start,script end,promise1,promise2,setTimeout
Promise的then属于微任务。
console.log('script start')
let promise = new Promise(function(resolve,reject){
console.log('promise')
resolve()
}).then(function(){
console.log('resolve')
})
setTimeout(function(){
console.log('settimeout');
})
console.log('script end')
// promise 的then属于宏任务,但是new Promise是同步的。
执行结果script start,promise,script end,resolve,settimeout
# 内存空间
JavaScript具有自动垃圾回收的机制。
# 数据结构
# 堆
堆数据是一种树状结构。顺序不影响使用,类似于书架取书。
# 栈
JavaScript中没有严格意义上区分栈内存和堆内存。可以认为JavaScript中的所有数据都是保存在堆内存中,但是在执行上下文的场景中,执行顺序借用了栈数据结构的额存取方式。
存取方式:先进后出
# 队列
队列是一种先进先出的数据表结构。
栈内存储基础类型,Number String Null Undefined Boolean。
堆内一般存储引用数据类型,对象、数组等。
# 调用栈、执行上下文
# 调用栈
调用栈是解释器追踪函数执行流的一种机制。
# 执行上下文
执行上下文是代码的执行环境,通常由三种(Global、function、eval)。
JavaScript引擎会以栈的方式处理他们。
代码执行过程中,每生成一个执行上下文,都会将这个上下文放到栈顶,待执行完毕,就会出栈。
除了依赖调用栈来处理函数,还会通过任务队列(task quene)来搞定另外的代码执行。
- 单线程
- 同步执行,只有栈顶的上下文处于执行状态,其他的都需要等待
- 全局上下文只有唯一一个,浏览器关闭时出栈
- 函数的执行上下文没有个数限制
- 每次单个函数的调用,就会创建新的执行上下文,调用自身函数也会创建
# 执行上下文生命周期
- 创建阶段
在这个阶段,执行上下文会分别创建变量对象、建立作用域链、确定this的指向。
变量对象的创建又经历了以下几个过程:
1、建立arguments对象。检查当前上下文的参数,建立该对象下的属性和属性值。
2、检查当前上下文的函数声明。并在变量对象中建立对应属性,属性值为函数所在内存地址。如果属性名已经存在,则会用心属性值覆盖。
3、检查当前上下文的变量声明。每找到一个变量声明,就在变量对象中以变量名建立一个属性,如果属性已经存在,为了防止同名的函数被修改为undefined,则会直接跳过,原有属性值不会被修改。(所以说函数的优先级更高)
- 代码执行阶段
创建完成后,开始执行代码,完成变量赋值、函数引用、执行其他代码。
function greeting() {
sayHi();
}
function sayHi() {
return "Hi!";
}
greeting();
...
1、上面代码在执行时,会先进入Global的执行上下文中,这个执行上下文被放到了调用栈的栈底
2、代码继续执行(函数声明略过),当执行到greeting时,创建一个greeting执行上下文,放到调用栈中
3、在greeting中,调用了sayHi,同样会创建sayHi执行上下文中,放到调用栈
4、sayHi执行完,出栈
6、greeting 在sayHi函数执行完成之后并无其他代码需要执行,栈中删除greeting
...
# 大致过程
1、同步任务在主线程上执行,形成执行栈(call stack)
2、主线程外还有一个任务队列,异步任务执行完成之后,就在任务队列放置一个时间
3、执行栈上的任务执行完毕,系统读取任务队列
4、任务队列执行完毕,重新检查主线程上的任务(循环往复)
TIP
对于AJAX、setTimeout这些,浏览器提供的webapi,在执行完成之后会放到callbackquene,等待系统读取