首先,我们来明确几个概念:
①JS是单线程的,有一个主进程和一个调用栈,主进程也就是JS的运行时进程
②浏览器内核是多线程的,有很多个模块,其中有一个很重要的webcore模块,对JS中的大多异步操作进行处理
③当JS的调用栈中遇到异步操作时,会将任务交给浏览器的其他模块,并不是自己放到哪个犄角旮旯让它自己等着
④JS中的异步大致就是 DOM操作、ajax请求、setTimeout等
其中DOM操作会交给webcore中的DOM Binding模块处理,
ajax请求会交给webcore中network模块处理,
setTimeout等定时器会交给webcore中的timer模块处理,这些模块都是一个独立的线程
⑤事件队列并不是JS的,而是浏览器的一个事件处理模块
console.log(1);
setTimeout(function f(){
console.log(2);
},5000);
console.log(3);
首先,这段代码的执行上下文进入调用栈,通俗的说,执行上下文就是用来标志当前作用域的,当作用域内的语句执行完就会出栈。
然后,console.log(1)进入调用栈,因为它并不是异步语句,直接打印 1 ,此时栈内剩下 执行上下文
然后,setTimeout() 函数进入调用栈,发现它是异步的(坏人,我要把你上交给国家),需要交给其他模块处理,于是,立刻将其出栈,并把它送到 浏览器的timer 模块,timer模块发现送来一个setTimeout函数,参数是5秒,心想,又有犯人(function f(){console.log(2)})来了,这次关5秒,好吧。 此时,调用栈内还是只有 执行上下文
然后,遇到console.log(3),进栈,不是异步,直接打印 3,此时栈内剩下 执行上下文
然后,JS主进程发现 程序语句运行完了,而且异步的函数中也没有引用作用域之内的某个变量,那就说明这个作用域没啥事了啊,那就清空吧,所以执行上下文出栈,就把内存清了
此时,时间过去了0.00000001毫秒(瞎说的),然后就没事了,那就闲着呗,就等啊等啊等啊等啊等啊等啊等啊等啊等啊等啊等啊等啊等啊等啊等啊等啊等啊等啊等啊等啊等啊。。。。。。。。。。。。。
5秒过去了,此时,timer模块想,时间到了,function f(){console.log(2)}你得出狱了,出去吧。于是,setTimeout中的那个回调函数被关了5秒终于被放出来了,然后,它得回家啊,家是哪?JS的调用栈啊,那是函数的归宿啊,怎么回去呢,来的时候直接把我扔过来,回去只有走路(事件队列)了,发现这条路的出口只有在家里没人进(调用栈没语句进栈)的时候才开,而且路很窄,一次只能过一个人,不过运气好的是前面没有人,不用排队,然后就走,走到路口,路口还开着,运气真好,然后就走出了那条路,站到了家门口(还没进去)。
然后,f 是一个函数,那它就是一个作用域,也是其内部语句的执行上下文,所以 标志作用域的 执行上下文 f 进栈。
最后,console.log(2) 进栈,不是异步的,立即执行,然后 f 作用域也执行完了,出栈,清除。大结局。
我们来总结一下:
1、所有代码都要经过JS调用栈执行
2、遇到异步事件,交给浏览器的其他模块管理
3、任务队列之中走的是回调函数
4、调用栈中的任务执行完,再放事件队列中的任务进入
再来明确点概念:
①任务队列,也就是事件队列,分为 宏任务(macro-task) 和 微任务(micro-task)
②macro-task 包括:script、setTimeout、setInterval、setImmediate(node)、requestAnimationFrame、I/O流、UI 渲染(嗯,UI渲染也是异步的)
③micro-task 包括:process.nextTick(node)、Promises、Object.observer、MutationObserver
我们先口述一下现在的事件执行顺序:
①先顺序从上向下执行当前全局上下文
②遇到异步事件就将其交给对应的浏览器模块
③浏览器的模块处理完之后,宏任务放入宏任务队列,微任务放入微任务队列
④当函数调用栈清空,开始执行任务队列,先执行微任务队列,执行完微任务队列再执行宏任务队列
⑤当执行任务队列时,可以认为重新开了一个空的宏任务队列和一个空的微任务队列,将新产生的异步任务最终放入新的任务队列,当前任务队列执行完成后,当前宏队列和微队列就清除,然后再去执行新的微任务队列,执行新的宏任务队列,新开微队列,新开宏队列。。。。一直循环下去,直到任务队列全部为空。
OK,口述完毕,拜拜,别想让我再搞一个例子,懒。