1.JavaScript是一门单线程语言,单线程意味着他只有一个栈,一次只能去做一件事情,所以要知道他是如何去处理异步问题还得搞清楚JavaScript的代码执行运行机制。
首先我们先写一段简单同步线程的代码
function one(n){console.trace(n);}
function tow(n){one(n)}
function three(n){tow(n)}
three(3)
如图所示callStack中是正在执行的代码,我们使用console.trace()打印此时调用站(callStack)的情况,下图的执行结果让我们了解到函数的调用顺序是一种后进先出的规律。
2.那么它是如何解决异步问题的呢?(task queues)
我们先提一个概念 “任务队列(task queues)“
由以下代码我们可以知道settimeout会延迟一秒执行,他在执行前会被放入一个任务队列当中(task queues),并不会直接去执行,等待 callStack 中的事件全部完成的时候 event loop才会去将taskQueues中的事件调到callStack中执行
console.log('start'); setTimeout(()=>{ console.log('waiting') } , 1000) console.log('end');
输出结果 由于setTimeout没有返回值所以他会先抛出undefined再等待一秒钟后打印
3.异步队列也有执行优先级!(Microtasks)
1.macrotask
在等待回调的任务当中他们也分两种任务,macrotask queue就是我们常说的task queue。Macrotask包括了setTimeout, Dom 操作 click/onLoad/mouse事件绑定,fetch then response这类操作。实际上这些都是浏览器提供的API,所以在执行时是有它们单独的线程去进行操作。举个例子,setTimeout()设置了2s的延迟,是浏览器设置了timer来计时,是另外的线程在等待2秒,js主线程不受影响,2s后回调函数再进入task queue。
(function() { console.log('this is the start'); setTimeout(function cb() { console.log('this is a msg from call back'); }); console.log('this is just a message'); setTimeout(function cb1() { console.log('this is a msg from call back1'); }, 0); console.log('this is the end'); })();
event loop会将先有返回的任务放入callstack中 如果cb若和cb1无时间差则按照先进先出的执行顺序
2.Microtask
ES6提供了Promise来进行异步操作。microtask。同上也有一个队列(job queue)来处理microtask。job queue拥有更高的优先级。每个task结束后,都会检查microtask队列是否有任务在等待执行,根据先进先出,依次执行。
console.log('script start'); setTimeout(function() { console.log('setTimeout'); }, 0); Promise.resolve().then(function() { console.log('promise1'); }).then(function() { console.log('promise2'); });
由于macrotask的队列(task queues)比 Microtask 的队列(job queues)优先级低所以setTImeout的打印内容最后执行
3.总结 event loop
eventloop 当callstack为空时去检查队列是否有需要执行的任务,执行顺序跟根据先进先出的规则执行,执行的Microtask(Promise) 队列比macrotask(setTimeout/DOM events/fetch)队列优先级高,Microtask 中的任务会先执行,当Microtask中的队列任务全部执行完毕之后才会去执行macrotask队列中的任务,当然最大的前提还是callstack得先执行完成
while (queue.waitForMessage()) { queue.processNextMessage(); }