Event Loop

109 阅读2分钟

JavaScript是单线程

为什么是单线程

JavaScript的主要用途是与用户交互和操作DOM。如果它是多线程的,一个线程在删除DOM节点,另一个线程在增加DOM节点的内容,那么这个时候我们就不知道以哪个线程为准了,所以JavaScript一直都是单线程的。

PS:

为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。

JavaScript的运行机制

  1. 主线程从上到下执行代码,形成一个执行栈,遇到异步任务暂时挂起。
  2. 异步任务有了运行结果,就放入任务队列
  3. 主线程中的所有同步任务执行完毕,即执行栈为空的时候,去读取任务队列
  4. 任务队列中的第一个任务进入执行栈,开始执行,执行完毕再次重复3、4两个步骤。

异步任务

我们知道了整体上同步任务和异步任务的执行顺序,再细分下去,异步任务又分为宏任务微任务

  • 宏任务(macrotasks):script(整体代码), setTimeout, setInterval, I/O(异步请求), UI rendering
  • 微任务(microtasks):promises、MutationObserver

事件循环

主线程先执行同步任务同步任务执行完之后,不断的循环检查异步队列,将异步队列中的内容放入主线程执行。检查异步队列的时候,先检查微任务队列,如果不为空则一次性执行完所有的微任务,然后进入下一次循环。

练习题

总结了这么多,验证一下自己是否真的理解了呢

setTimeout(()=>{
    console.log('timer1')

    Promise.resolve().then(function() {
        console.log('promise1')
    })
}, 0)

setTimeout(()=>{
    console.log('timer2')

    Promise.resolve().then(function() {
        console.log('promise2')
    })
}, 0)
输出内容:timer1,promise1,timer2,promise2

var outer = document.querySelector('.outer');
var inner = document.querySelector('.inner');

new MutationObserver(function() {
  console.log('mutate');
}).observe(outer, {
  attributes: true
});

function onClick() {
  console.log('click');

  setTimeout(function() {
    console.log('timeout');
  }, 0);

  Promise.resolve().then(function() {
    console.log('promise');
  });

  outer.setAttribute('data-random', Math.random());
}

inner.addEventListener('click', onClick);
outer.addEventListener('click', onClick);

//输出顺序click promise mutate click promise mutate timeout timeout
console.log('script start');

setTimeout(function () {
    console.log('setTimeout---0');
}, 0);

setTimeout(function () {
    console.log('setTimeout---200');
    setTimeout(function () {
        console.log('inner-setTimeout---0');
    });
    Promise.resolve().then(function () {
        console.log('promise5');
    });
}, 200);

Promise.resolve().then(function () {
    console.log('promise1');
}).then(function () {
    console.log('promise2');
});
Promise.resolve().then(function () {
    console.log('promise3');
});
console.log('script end');
//输出结果script start、script end、promise1、promise3、promise2、setTimeout---0、
setTimeout---200、promise5、inner-setTimeout---0

HTML5标准规定了setTimeout()的第二个参数的最小值(最短间隔),不得低于4毫秒,如果低于这个值,就会自动增加。在此之前,老版本的浏览器都将最短间隔设为10毫秒。另外,对于那些DOM的变动(尤其是涉及页面重新渲染的部分),通常不会立即执行,而是每16毫秒执行一次。这时使用requestAnimationFrame()的效果要好于setTimeout()。