js运行机制
JavaScript是单线程
这重设计避免了一个线程在DOM节点上添加内容,另一个线程删除了这个节点的尴尬事件。
任务列队
主线程执行“同步任务”,被主线程挂载起来的是“异步任务”,后者一般是放在一个叫“任务队列”的数据结构中。
JavaScript的运行机制
(1)所有同步任务都在主线程上执行,形成一个执行栈。
(2)主线程之外,还有一个“任务队列”,只要异步任务有了运行结果,就在“任务队列”之中放置一个事件。
(3)一旦“执行栈”中的所有同步任务执行完毕了,系统就会读取“任务队列”,看看里面有哪些事件,那些对应的异步任务,预示结束等待状态,进入执行栈,开始执行。
(4)主线程不断重复上面的三步。(事件循环)。
parse markup and set up DOM(解析标记并设置DOM)
check for event at head of queue(检查队列头部的事件)
事件和回调函数
其实任务列队就是一个事件列队,因为一般我们绑定一个事件,比如点击事件,都是在某个时刻才触发的,这个时候就得放到任务队列里面,等待执行,而在某个DOM节点上绑定了事件,就要有相应的回调函数。
所谓回调函数就是那些被挂载起来,等待执行的代码,才会执行任务队列里的异步任务,一般是按照队列的“先进先出”顺序执行,但是因为存在定时器,所以主线程要检查执行时间,只有到了规定的时间,才能返回主线程。
定时器
setTimeout作为BOM API,它负责设定一个计时器,到点把要执行的函数添加到任务队列,它不能保证立即执行,什么时候执行取决与事件循环什么时候把这个函数调度到主线程。这就是setTimeout不能精确定时执行的原因。
//代码1
console.log('先执行这里');
setTimeout(() => {
console.log('执行啦')
},0);
setTimeout(fn,0)
的含义是,指定某个任务在主线程最早可得的空闲时间执行,意思就是不用再等多少秒了,只要主线程执行栈内的同步任务全部执行完成,栈为空就马上执行。
即便主线程为空,0毫秒实际上也是达不到的。根据HTML的标准,最低是4毫秒。
任务队列包含宏任务以及微任务
- 宏任务(macrotask)::
script(整体代码)、setTimeout、setInterval、UI 渲染、 I/O、postMessage、 MessageChannel、setImmediate(Node.js 环境)
- 微任务(microtask):
Promise、 MutaionObserver、process.nextTick(Node.js环境)
事件循环
- 整体script作为第一个宏任务进入主线程,将遇到的宏任务分到宏任务Event Queue中,将遇到的微任务分到微任务Event Queue中。结束第一轮宏任务结束。
- 将微任务中的任务执行完毕后,第一轮事件循环结束。
- 第二轮就是执行宏任务,继续将里面的宏任务和微任务剥离来放入各自的列队中,依次循环。
setTimeout(function() {
console.log(1);
setTimeout(function() {
console.log(6);
}, 0);
new Promise(function(a, b) {
console.log(7);
for (var i = 0; i < 10; i++) {
i == 9 && a();
}
console.log(8)
}).then(function() {
console.log(9)
});
}, 0);
new Promise(function(a, b) {
console.log(2);
for (var i = 0; i < 10; i++) {
i == 9 && a();
}
console.log(3)
}).then(function() {
console.log(4)
});
console.log(5);
结果是2,3,5,4,1,7,8,9,6