因为JS的主要作用是和用户互动,在前端的javascript操作dom时,并不会像后端一样通过锁或者其他的方式来保证这种彼此影响的操作的先后顺序,所以为了避免复杂性和确保对于Dom操作的唯一性,使用单线程是一个利大于弊的方案。
常见的宏任务
- script(整体代码)
- setTimout click
- setInterval
- setImmediate(node 独有)
- requestAnimationFrame(浏览器独有)
- IO
- UI render(浏览器独有)
常见的微任务
- process.nextTick(node 独有)
- Promise.then()
- Object.observe
- MutationObserver 为了协调事件、用户交互、脚本、渲染、网络等,用户代理必须使用本节中描述的事件循环。每个[代理] 都有一个关联的事件循环,这是该代理独有的。事件循环中的任务被分为宏任务和微任务,是为了给高优先级任务一个插队的机会:微任务比宏任务有更高优先级。
浏览器的事件循环分为同步任务和异步任务;所有同步任务都在主线程上执行,形成一个函数调用栈(执行栈),而异步则先放到任务队列(task queue)里,任务队列又分为宏任务(macro-task)与微任务(micro-task)。下面的整个执行过程就是事件循环 微任务比宏任务有更高优先级。 先执行宏任务,然后执行该宏任务产生的微任务,若微任务在执行过程中产生了新的微任务,则继续执行微任务,微任务执行完毕后,再回到宏任务中进行下一轮循环。 宏任务大概包括::script(整块代码)、setTimeout、setInterval、I/O、UI交互事件、setImmediate(node环境)
微任务大概包括::new promise().then(回调)、MutationObserver(html5新特新)、Object.observe(已废弃)、process.nextTick(node环境) 若同时存在promise和nextTick,则先执行nextTick
执行过程 先从script(整块代码)开始第一次循环执行,接着对同步任务进行执行,直到调用栈被清空,然后去执行所有的微任务,当所有微任务执行完毕之后。再次从宏任务开始循环执行,直到执行完毕,然后再执行所有的微任务,就这样一直循环下去。如果在执行微队列任务的过程中,又产生了微任务,那么会加入整个队列的队尾,也会在当前的周期中执行。