event loop是什么
Event Loop 是 JavaScript 异步编程的核心思想。
我们注意到,在异步代码完成后仍有可能要在一旁等待,因为此时程序可能在做其他的事情,等到程序空闲下来才有时间去看哪些异步已经完成了。所以 JavaScript 有一套机制去处理同步和异步操作,那就是事件循环 (Event Loop)。
用文字描述的话,大致是这样的:
- 所有同步任务都在主线程上执行,形成一个执行栈 (Execution Context Stack)
- 而异步任务会被放置到 Task Table,也就是上图中的异步处理模块,当异步任务有了运行结果,就将该函数移入任务队列。
- 一旦执行栈中的所有同步任务执行完毕,引擎就会读取任务队列,然后将任务队列中的第一个任务压入执行栈中运行。
主线程不断重复第三步,也就是 只要主线程空了,就会去读取任务队列,该过程不断重复,这就是所谓的 事件循环。
为什么javascript是单线程的
我们都知道 JavaScript 是一门 单线程 语言,也就是说同一时间只能做一件事。这是因为 JavaScript 生来作为浏览器脚本语言,主要用来处理与用户的交互、网络以及操作 DOM。这就决定了它只能是单线程的,否则会带来很复杂的同步问题。
假设 JavaScript 有两个线程,一个线程在某个 DOM 节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?
既然 Javascript 是单线程的,它就像是只有一个窗口的银行,客户不得不排队一个一个的等待办理。同理 JavaScript 的任务也要一个接一个的执行,如果某个任务(比如加载高清图片)是个耗时任务,那浏览器岂不得一直卡着?为了防止主线程的阻塞,JavaScript 有了 同步 和 异步 的概念。
同步
如果在一个函数返回的时候,调用者就能够得到预期结果,那么这个函数就是同步的。也就是说同步方法调用一旦开始,调用者必须等到该函数调用返回后,才能继续后续的行为。下面这段段代码首先会弹出 alert 框,如果你不点击 确定 按钮,所有的页面交互都被锁死,并且后续的 console 语句不会被打印出来。
异步
如果在函数返回的时候,调用者还不能够得到预期结果,而是需要在将来通过一定的手段得到,那么这个函数就是异步的。比如说发一个网络请求,我们告诉主程序等到接收到数据后再通知我,然后我们就可以去做其他的事情了。当异步完成后,会通知到我们,但是此时可能程序正在做其他的事情,所以即使异步完成了也需要在一旁等待,等到程序空闲下来才有时间去看哪些异步已经完成了,再去执行。
这也就是定时器并不能精确在指定时间后输出回调函数结果的原因。
执行栈和任务队列
执行栈
当我们调用一个方法的时候,JavaScript 会生成一个与这个方法对应的执行环境,又叫执行上下文(context)。这个执行环境中保存着该方法的私有作用域、上层作用域(作用域链)、方法的参数,以及这个作用域中定义的变量和 this 的指向,而当一系列方法被依次调用的时候。由于 JavaScript 是单线程的,这些方法就会按顺序被排列在一个单独的地方,这个地方就是所谓执行栈。
执行栈中的代码永远最先执行
任务队列
事件队列是一个存储着 异步任务 的队列,其中的任务严格按照时间先后顺序执行,排在队头的任务将会率先执行,而排在队尾的任务会最后执行。事件队列每次仅执行一个任务,在该任务执行完毕之后,再执行下一个任务。执行栈则是一个类似于函数调用栈的运行容器,当执行栈为空时,JS 引擎便检查事件队列,如果事件队列不为空的话,事件队列便将第一个任务压入执行栈中运行。
当执行栈中的代码执行完毕,会在执行宏任务队列之前先看看微任务队列中有没有任务,如果有会先将微任务队列中的任务清空才会去执行宏任务队列
面试题利器
- 在执行时会先进行宏任务,在宏任务如果有
setTimeout、fetch、ajax、事件回调、setInteral,setImmediate等会加入消息队列,然后查看有没有微任务,eg.promise、async、await等就先执行微任务,等微任务执行完之后,在进行消息队列的内容 Promise.then()相当于每次都返回一个promise对象,但是要执行的事件要进入异步,加入微任务列表Promise.then()函数执行中如果存在setTimeout,会等待定时器进行完成之后在进入下一个进程.then- 只有
node环境中会存在process.nextTick以及宏任务的setImmediate - 一般情况下
process.nextTick会先于promise.then执行 requestAnimationFrame会晚于.then执行,早于消息队列内容执行setTimeout、setImmediate并不能决定哪个优先执行,一般按照压入消息队列的顺序进行执行- 对于
async、await的理解,async相当于一个promise的excutor函数,同步执行,await相当于promise的.then函数,异步执行。
setTimeout(() => {
console.log('A'); //压入消息队列 //4、A
}, 0);
var obj = {
func: function() {
setTimeout(function() {
console.log('B'); //压入消息队列 //5、B
}, 0);
return new Promise(function(resolve) {
console.log('C'); //1、C
resolve();
});
},
};
obj.func().then(function() {
console.log('D'); //压入微任务队列 //3、D
});
console.log('E'); //2、E
得心应手
new Promise((resolve,reject)=>{
console.log("promise1") //1、promise1
resolve()
}).then(()=>{ //压入微任务队列 因为之后没有了
console.log("then11") //2、then11
new Promise((resolve,reject)=>{
console.log("promise2") //3、promise2
resolve()
}).then(()=>{
console.log("then21") //4、then21
}).then(()=>{
console.log("then23") //6、then23
})
}).then(()=>{
console.log("then12") //5、then12
})
游刃有余
如果.then里面新的promise是被return出去的就有当这个promise执行完才会走下一个微任务
new Promise((resolve,reject)=>{
console.log("promise1") //1、promise1
resolve()
}).then(()=>{
console.log("then11") //2、then11
return new Promise((resolve,reject)=>{
console.log("promise2") //3、promise2
resolve()
}).then(()=>{
console.log("then21") //4、then21
}).then(()=>{
console.log("then23") //5、then23
})
}).then(()=>{
console.log("then12") //6、then12
})
炉火纯青
new Promise((resolve,reject)=>{
console.log("promise1") //1、promise1
resolve()
}).then(()=>{
console.log("then11") //3、then11
new Promise((resolve,reject)=>{
console.log("promise2") //4、promise2
resolve()
}).then(()=>{
console.log("then21") //6、then21
}).then(()=>{
console.log("then23") //7、then23
})
}).then(()=>{
console.log("then12") //8、then12
})
new Promise((resolve,reject)=>{
console.log("promise3") //2、promise3
resolve()
}).then(()=>{
console.log("then31") //5、then31
})
登峰造极
如果.then里面新的promise是被return出去的就有当这个promise执行完才会走下一个微任务
return一个promise会延迟两个.then
new Promise((resolve,reject)=>{
console.log("promise1") //1、promise1
resolve()
}).then(()=>{
console.log('2') //3、2
}).then(()=>{
console.log("then11") //5、then11
return new Promise((resolve,reject)=>{
console.log("promise2") //6、promise2
resolve()
})
}).then(()=>{
console.log('3') //10、3
}).then(()=>{
console.log('4') //12、4
})
new Promise((resolve,reject)=>{
console.log("promise3") //2、 promise3
resolve()
}).then(()=>{
console.log("then31") //4、then31
}).then(()=>{
console.log("then33") //7、then33
}).then(()=>{
console.log("then34") //8、then34
}).then(()=>{
console.log("then35") //9、then35
}).then(()=>{
console.log("then37") //11、then37
}).then(()=>{
console.log("then39") //13、then39
})
终极变态
async function async1() {
console.log("async1 start"); //2、async1 start
await async2();
console.log("async1 end"); //压入微任务 //7、async1 end
}
async function async2() {
console.log( 'async2'); //3、async2
}
console.log("script start"); //1、script start
setTimeout(function () {
console.log("settimeout"); //压入消息列表 //9、settimeout
});
async1()
new Promise(function (resolve) {
console.log("promise1"); //4、promise1
resolve();
}).then(function () { //压入微任务 //8、promise2
console.log("promise2");
});
setImmediate(()=>{ //压入消息列表
console.log("setImmediate") //10、setImmediate
})
process.nextTick(()=>{ //压入微任务 //6、process
console.log("process")
})
console.log('script end'); //5、script end