# EventLoop 事件循环

由于JS是同步执行的单线程语言,没有异步概念。
JS做的就是一直循环的从事件队列中拿出第一个任务,放到运行栈中去运行。 这样循环往复的行为就被称为:事件循环(Event Loop)

# 如何实现异步呢?

JS中的异步有两种情况:
宏任务:通过宿主环境(浏览器/node)配合来实现的。
微任务:通过在运行栈后,事件队列前,加入一个任务队列来实现。

# 宏任务

就像一家生意火爆的火锅店,很多人在排队(事件队列),服务员喊号,到号的人进去开心的吃火锅(运行栈)。
等了很久终于叫到了你,但你突然有件急事要解决,不能进去吃。火锅店又不可能等你半个小时,这时你跟服务员说(告诉宿主环境)。你重新帮我排个号吧(添加宏任务,等同于setTimeout(排号, 0)),服务员拿出小pad帮我重新排了号,我现在又进到了队伍的最后,等待重新叫号....

  • JS执行到异步函数(假设为setTimeout),把回调函数告诉宿主环境,并告诉他什么时机插入事件队列
  • 等到了跟JS约定的时机,宿主环境回调函数插入到事件队列
  • JS在下一个事件循环中,把事件队列中的第一个任务放到运行栈进行执行,就能执行到这个回调函数

# 微任务

你进入了火锅店(运行栈),开始吃火锅(本次的任务)。但吃火锅的过程中你可以做很多事,拿调料、等朋友、叫服务员加菜...(微任务,加入任务队列)在你没吃完之前,下一个人没法到你这个座位吃饭(事件队列中的任务要等),只要脸皮够厚,可以吃到火锅店关门...(这就是微任务的弊端

  • 引进一个新队列:任务队列(里面的任务,在完成运行栈的任务后,在从事件队列拿下一个任务之前进行执行)
  • 微任务,插入到任务队列(Promise就是微任务)
  • 只有运行栈任务队列中没有任务,才会重新从事件队列中取出下一个任务

总结一下,在整个JS的事件循环中,共用到了2个队列,一个运行栈

  1. 事件队列(里面存放:同步任务宏任务的回调
  2. 任务队列(里面存放:执行微任务时插入的任务。运行栈执行完之后先从这里取任务执行)
  3. 运行栈(先从事件队列中取的一个任务,执行过程中又会往事件队列任务队列中插入任务,执行完从任务队列取任务,任务队列中也没任务,就进入下一个循环)

# 事件循环完整流程图

图示

# 整几道题感受下事件循环

# 1. 写出相应的输出结果

// 只有微任务
const promise = new Promise((resolve, reject) => {
  console.log(1); // 同步代码
  resolve()
});

promise.then(() => {
  console.log(2); // .then异步代码(微任务,放入任务队列)
});

console.log(3); // 同步代码

// 结果:1 3 2
// 只有宏任务
console.log(1); // 同步代码

// 异步代码(宏任务,交给宿主环境,并告诉他1000ms后插入事件队列)
setTimeout(() => {
  console.log(2);
}, 1000);

console.log(3); // 同步代码

// 结果:1 3 2
// 微任务、宏任务结合

// 异步代码(宏任务,交给宿主环境,并告诉他1000ms后插入事件队列)
setTimeout(() => {
  console.log(1);
}, 1000);

const promise = new Promise((resolve, reject) => {
  console.log(2); // 同步代码
  resolve()
});

promise.then(() => {
  console.log(3); // .then异步代码(微任务,放入任务队列)
});

console.log(4); // 同步代码

// 结果:2 4 3 1
// 最后一道大家自己试着分析一下
console.log('start');

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

  new Promise((reslove, reject) => {
    console.log('promise');
    setTimeout(() => {
        console.log('timer3')
        reslove();
    }, 0)
  }).then(() => { // 只有当promise状态不为pedding的时候,then才可以成功压入队列
    console.log('then');
  });

}, 0);

setTimeout(() => {
  console.log('timer2')
}, 0);

console.log('end');

// 结果:start end timer1 promise timer2 timer3 then

# 2. 实现一个LazyMan

实现一个LazyMan(),可以实现一下效果:

LazyMan('Jack');
// hi, I am Jack


LazyMan('Jack').eat('dinner');
// hi, I am Jack
// eat dinner


LazyMan('Jack').sleep(1000).eat('dinner');
// hi, I am Jack
// wait 1000ms
// eat dinner


LazyMan('Jack').eat('dinner').sleepFirst(1000);
// wait 1000ms
// hi, I am Jack
// eat dinner
// 实现
class lazyMan {
  constructor(name) {
    this.task = [];

    this.sayHi(name);
    
    setTimeout(() => {
      this.next();
    }, 0)
  }

  next() {
    let task = this.task.shift();
    task && task();
  }

  sayHi(name) {
    let task = () => {
      console.log(`hi, I am ${name}`);
      this.next();
    };
    this.task.push(task)
  }

  eat(food) {
    let task = () => {
      console.log(`eat ${food}`);
      this.next();
    };
      
    this.task.push(task);
    return this;
  }

  sleep(time) {
    let task = () => {
      setTimeout(() => {
        console.log(`wait ${time}ms`);
        this.next();
      }, time)
    }
    this.task.push(task);
    return this;
  }

  sleepFirst(time) {
    let task = () => {
      setTimeout(() => {
        console.log(`wait ${time}ms`);
        this.next();
      }, time)
    }
    this.task.unshift(task);
    return this;
  }
}

function LazyMan(name) {
  return new lazyMan(name);
}


// 测试效果
LazyMan('jack').sleep(1000).eat('dinner').sleepFirst(1000);

如果觉得内容对你有帮助,请点个关注,你们的鼓励是我持续更新下去的动力,比心。

如需转载请注明出处,感恩。

更多内容可先关注GitHub (opens new window)