JS学习笔记:(三)JS执行机制
首先我们先明确一点:JavaScript是一门单线程语言。单线程也就是说同一时间只能执行一个任务,所有的任务都必须排队顺序执行。那么如果一个任务耗时很长,阻塞了其它任务的执行,就会给用户造成不友好的体验。那么JS是如何解决这个问题的呢?(注:H5提出了Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。)
要搞清除这个问题,我们要明确知道同步任务和异步任务,异步执行机制,任务队列,Event Loop这些知识点。
一、同步任务和异步任务
同步任务:是指在主线程执行的任务,只有前一个任务执行完成,才能执行下一个任务。
异步任务:指的是不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。如:setTimeout, dom事件,ajax事件,promise,async await
二、异步执行机制
具体来说,异步执行的运行机制如下。(同步执行也是如此,因为它可以被视为没有异步任务的异步执行。)
- 所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
- 主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
- 一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
-
主线程不断重复上面的第三步。
只要主线程空了,就会去读取"任务队列",这就是JavaScript的运行机制。这个过程会不断重复。
三、任务队列
首先我们要明确任务队列是一个先进先出的数据结构,里面存的是事件,只要异步任务可以执行了就会在任务队列注册一个事件。
只有执行栈中的所有的同步任务执行完,才会去读取任务队列。异步任务必须指定回调函数,当主线程开始执行异步任务,就是执行对应的回调函数。
四、Event Loop
主线程从"任务队列"中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)。
为了更好地理解Event Loop,请看下图(转引自Philip Roberts的演讲《Help, I'm stuck in an event-loop》)。
上图中,主线程运行的时候,产生堆(heap)和栈(stack),栈中的代码调用各种外部API,它们在"任务队列"中加入各种事件(click,load,done,setTimeout)。只要栈中的代码执行完毕,主线程就会去读取"任务队列",依次执行那些事件所对应的回调函数。
五、宏任务与微任务
除了广义的同步任务和异步任务,任务还有更精细的定义:
- macro-task(宏任务):包括整体代码script,setTimeout,setInterval
-
micro-task(微任务):Promise,process.nextTick
事件循环,宏任务,微任务的关系如图所示:
总结
- 首先JS判断一个任务是同步任务还是异步任务;
- 如果是同步任务,就进入主线程,加入执行栈;
- 如果是异步任务,就加入Event Table,如果异步任务可以执行,就在Event Quene中注册一个相应的事件,并指定回调函数;
-
主线程的执行栈中的任务全部执行完毕,就会读取任务队列,如可以执行则立即把相应的回调函数加入主线程执行栈。然后就会一直循环判断第4步(由JS引擎的monitor process监控)。
按照宏任务和微任务这种分类方式,JS的执行机制是
- 执行一个宏任务,过程中如果遇到微任务,就将其放到微任务的【事件队列】里
-
当前宏任务执行完成后,会查看微任务的【事件队列】,并将里面全部的微任务依次执行完
参考文章: