前端点滴(JS进阶)(二)---- JavaScript 同步与异步,不同环境下的 Event Loop,Promise基础

1. JavaScript 单线程

JS的单线程的概念

JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。那么,为什么JavaScript不能有多个线程呢?这样能提高效率啊。

JavaScript的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征。

任务队列(消息队列)

单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。JavaScript语言的设计者意识到这个问题,将所有任务分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。

可以参考:https://blog.csdn.net/weixin_42614080/article/details/90346489

2. 同步任务说明

同步:在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务。如果在函数A返回的时候,调用者就能够得到预期的结果(即拿到了预期的返回值或者看到了预期的效果),那么这个函数就是同步的。

console.log('hello');//执行后,获得了返回结果

其次,如果函数是同步的,即使调用函数执行任务比较耗时,也会一致等待直到得到执行结果。
在这里插入图片描述
由上述说明中发现:

  1. JavaScript 单线程。
  2. 同步任务在主线程上排队执行。
  3. 同步任务执行顺序不可改变(随着JavaScript代码自上而下执行)。

问题的引出:同步任务在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务。那么如果上一个任务解析时间很长,那么下面的代码就会被阻塞。
对于用户而言,阻塞就意味着"卡死",这样就导致了很差的用户体验,因此,说明了为什么JavaScript需要异步?那么js单线程又是如何实现异步的呢?
通过事件循环(event loop)实现 '异步’

(1)Event Loop 的引出

1. 什么是Event loop?

实际上event loop是一个执行模型,在不同的地方有不同的实现。浏览器和NodeJS基于不同的技术实现了各自的Event Loop。

  • 浏览器的Event Loop是在html5的规范中明确定义。
  • NodeJS的Event Loop是基于libuv实现的。可以参考Node的官方文档以及libuv的官方文档。

特点:

  • 一个事件循环(event loop)会有一个或多个任务队列(task queue)
    task queue 就是 macrotask queue
  • 每一个 event loop 都有一个 microtask queue
  • 一个任务 task 可以放入 macrotask queue 也可以放入 microtask queue 中

2. Event Loop 基本原理

  1. 所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
  2. 主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
  3. 一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
  4. 主线程不断重复上面的第三步。

主线程从"任务队列"中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)。只要主线程空了,就会去读取"任务队列",这就是JavaScript的运行机制。这个过程会循环反复。

3. 浏览器中的宏队列和微队列

宏队列,macrotask,也叫tasks。或者叫Task queue (任务队列) 一些异步任务的回调会依次进入macro task queue,等待后续被调用,这些异步任务包括:

  • setTimeout
  • setInterval
  • setImmediate (Node独有)
  • requestAnimationFrame (浏览器独有)
  • I/O
  • UI rendering (浏览器独有)

微队列,microtask,也叫jobs。实际上不是一个队列(html5的规范 Note4)。 另一些异步任务的回调会依次进入micro task queue,等待后续被调用,这些异步任务包括:

  • process.nextTick (Node独有)
  • Promise
  • Object.observe (废弃)
  • MutationObserver(注:这里只针对浏览器和NodeJS)

4. 浏览器的Event Loop工作原理

在这里插入图片描述
执行JavaScript代码的具体流程:

  1. 执行全局Script同步代码,这些同步代码有一些是同步语句,有一些是异步语句(比如setTimeout和setlnterval;DOM事件;ES6中的Promise;Ajax异步请求等);
  2. 全局Script代码执行完毕后,调用栈(主线程)(执行栈)会清空;
  3. 从微队列microtask queue中取出位于队首的回调任务,放入调用栈(主线程)中执行,执行完后microtask queue长度减1;
  4. 继续取出位于队首的任务,放入调用栈(主线程)中执行,以此类推,直到直到把microtask queue中的所有任务都执行完毕。注意,如果在执行microtask的过程中,又产生了microtask,那么会加入到队列的末尾,也会在这个周期被调用执行
  5. microtask queue中的所有任务都执行完毕,此时microtask queue为空队列,调用栈也为空;
  6. 取出宏队列macrotask queue中位于队首的任务,放入调用栈(主线程)中执行;
  7. 执行完毕后,调用栈(主线程)为空;
  8. 重复第3-7个步骤,直至将宏任务(宏队列任务)与微任务(微队列任务)全部执行完。
  9. 。。。

口诀: 自上而下,先同后异,先微后宏,谁先谁上

  • 自上而下:按照js代码自上而下执行,将任务分为宏队列任务以及微队列任务。

  • 先同后异:遵照自上而下先执行同步任务,后执行异步任务(队列任务)。

  • 先微后宏:在解决异步的过程中先解决微队列任务,再解决宏队列任务,注意: 要清楚队列执行顺序问题。总结如下:
    在这里插入图片描述

  • 谁先谁上:排在队首的先执行。

需要注意的是: 当前调用栈(执行栈)执行完毕时会立刻先处理所有微任务队列中的事件,然后再去宏任务队列中取出一个事件。同一次事件循环中,微任务永远在宏任务之前执行。

5. NodeJS中宏队列与微队列

NodeJS的Event Loop中,执行宏队列的回调任务有6个阶段,如下图:
在这里插入图片描述
各个阶段执行的任务如下:

  • timers阶段:这个阶段执行setTimeout和setInterval预定的callback
  • I/O callback阶段:执行除了close事件的callbacks、被timers设定的- callbacks、setImmediate()设定的callbacks这些之外的callbacks
  • idle, prepare阶段:仅node内部使用
  • poll阶段:获取新的I/O事件,适当的条件下node将阻塞在这里
  • check阶段:执行setImmediate()设定的callbacks
  • close callbacks阶段:执行socket.on(‘close’, …)这些callbacks

NodeJS中宏队列主要有4个
回调事件主要位于4个macrotask queue中:

  • Timers Queue
  • IO Queue
  • Check Queue
  • Close Callbacks Queue

这4个都属于宏队列,但是在浏览器中,可以认为只有一个宏队列,所有的macrotask都会被加到这一个宏队列中,但是在NodeJS中,不同的macrotask会被放置在不同的宏队列中。

NodeJS中微队列主要有2个:

  • Next Tick Queue:是放置process.nextTick(callback)的回调任务的
  • Other Micro Queue:放置其他microtask,比如Promise等

在浏览器中,也可以认为只有一个微队列,所有的microtask都会被加到这一个微队列中,但是在NodeJS中,不同的microtask会被放置在不同的微队列中。

6. NodeJS的Event Loop工作原理(轮询机制)

在这里插入图片描述
执行JavaScript代码的具体流程:

  1. 执行全局Script的同步代码
  2. 执行microtask微任务,先执行所有Next Tick Queue中的所有任务,再执行Other Microtask Queue中的所有任务
  3. 开始执行macrotask宏任务,共4(实际6)个阶段,从第1个阶段开始执行相应每一个阶段macrotask中的所有父任务(不包括子任务),注意,这里是所有每个阶段宏任务队列的所有任务,在浏览器的Event Loop中是只取宏队列的第一个任务出来执行,每一个阶段的macrotask任务执行完毕后,开始执行微任务。
  4. Timer Queue -> 步骤2 -> IO Queue -> 步骤2 -> Check Queue -> 步骤2 -> Close Callback Queue -> 步骤2 -> Timers Queue …

口诀: 自上而下,先同后异,分队列,按步骤,父执行,子等待

  • 分队列:将每一个任务分清,例如setTimeout() 属于timer Queue,process.nextTick() 属于 Next Tick Queue…
  • 按步骤:按照Timer Queue -> 步骤2 -> IO Queue -> 步骤2 -> Check Queue -> 步骤2 -> Close Callback Queue -> 步骤2 -> Timers Queue …
    在这里插入图片描述
  • 父执行,子等待:若父子同属一个任务队列,则父执行,子不执行(等待)

(2)ES6 Promise

Promise 对象用于处理异步请求,保存一个异步操作最终完成(或失败)的结果,用于解决异步问题的方法之一。

语法:

new Promise(
    /* executor(执行器) */
    function(resolve, reject) {...}
);

/*
      promise:承诺,约定
      resolve:解决,决定,下决心
      reject:拒绝,驳回,抵制 
*/

参数:

promise 构造函数接受一个 executor 函数作为参数,该函数的两个参数分别是 resolve 和 reject,实际上它们是两个函数
注意的是:(executor 函数 在 Promise 构造函数返回新对象之前被调用)说明 executor 函数属于同步任务。

存在情况:

  • resolve 函数被调用时,将 promise 对象从 “未完成” 变为 “成功” (即 pending --> fulfilled)

  • reject 函数被调用时,将 promise 对象从 “未完成” 变为 “失败” (即 pending --> rejected)

描述:

promise 对象是一个代理对象(代理一个值),被代理的值在 promise 对象创建时可能是未知的。它允许你为异步操作的成功和失败分别绑定相应的处理方法,使得异步方法可以像同步方法那样返回值,但不是立即返回最终执行结果,而是一个能代表未来出现的结果的 promise 对象。

上面提到过,一个 promise 对象有三个状态:

  • pending:初始状态,不是成功或失败状态

  • fulfilled:成功完成

  • rejected:失败

pending 状态可能触发 fulfilled 状态并传递一个值给相应的状态处理方法,也可能触发 rejected 状态并传递失败信息。当其中任一种情况发生时,promise 对象的 then 方法绑定的处理方法就会被调用。

(then 方法包含两个参数:onfulfilled 和 onrejected,也都是函数。当 promise 对象的状态为 fulfilled 时(也就是resolve 函数被调用时)调用,调用 then 的 onfulfilled 方法;反之,调用 onrejected 方法。所以异步操作的完成和绑定处理方法之间不存在竞争)。

以下是示意图:
在这里插入图片描述
then 的基本使用:

/* 写法一 */
var p = new Promise(function(resolve,reject){
	console.log(1)
	resolve("1");
});
p.then(function(res){
	console.log(res);
})  //=>  1 "1"

/* 写法二 */
new Promise(function(resolve,reject){
	resolve("1");
}).then(function(res){
	console.log(res);
}).then(function(){
	console.log(1);
})  //=>  "1"  1

/* 写法三 */
Promise.resolve("1").then(function(res){
console.log(res)
})  //=>  "1"

then 的多重嵌套:
实例:

new Promise(function(resolve){
       resolve(1);
})
  .then(function(value){
      return new Promise(function(resolve){
            resolve(2);
      })
      .then(function(value){
          return value;
      }) 
      .then(function(value){
          return value;  //如果此处没有return,则输出undefined
      })
  })
 .then(function(value){
     console.log(value)
 })  //=>  输出2,原因: return new Promise加上then的return值

(3)实例题(重点)(浏览器)

接下来就使使口诀看看是否奏效:
浏览器Event Loop口诀: 自上而下,先同后异,先微后宏,谁先谁上

/* 实例一 */
setTimeout(function(){console.log(1)},0);
console.log(2)

//=>  2  1  (先同后异)
/* 实例二 */
setTimeout(function () {
  console.log(3);
}, 0);

Promise.resolve().then(function () {
  console.log(2);
});
console.log(1);

//=>  1  2  3   (先同后异,先微后宏)
/* 实例三 */
setTimeout(function(){console.log(1)},0);

new Promise(function(resolve,reject){
   console.log(2);
   resolve();
}).then(function(){
	console.log(3)
}).then(function(){
	console.log(4)
});

process.nextTick(function(){console.log(5)});

console.log(6);

//=>  2  6  5  3  4  1    (先同后异,先微后宏)

分析Queue的执行顺序:nextTick Queue——>promise Queue

/* 实例四 */
setTimeout(function(){console.log(1)},0);

new Promise(function(resolve,reject){
   console.log(2);
   setTimeout(function(){resolve()},0)
}).then(function(){
	console.log(3);
}).then(function(){
	console.log(4);
});

process.nextTick(function(){console.log(5)});

console.log(6);   

//=>  2  6  5  1  3  4  (自上而下,先同后异,先微后宏,谁先谁上)

解释:promise的构造中,没有同步的resolve,因此promise.then在当前的执行队列中是不存在的,只有promise从pending转移到resolve,才会有then方法,而这个resolve是在一个setTimout时间中完成的,但是在其上还有一个setTimeout(自上而下,谁先谁上),因此先输出1,后输出3,4。

/* 实例五(比较经典) */
 Promise.resolve().then(()=>{
    console.log('1')
    setTimeout(()=>{     //2号setTimeout
      console.log('2')
    },0)
  })

  setTimeout(()=>{       //1号setTimeout
    console.log('3')
    Promise.resolve().then(()=>{
      console.log('4')
    })
  },0)

//=>  '1'  '3'  '4'  '2'  (自上而下,先微后宏,谁先谁上)

解释说明:
Step1:自上而下,promise 进入微队列,setTimeout进入宏队列。

  • mi:[Promise]
  • ma:[setTimeout()1]

Step2:先微后宏,先解决微队列任务,解决过程中又发现了宏任务(宏队列任务)。先输出 ‘1’,再将宏队列任务放入宏队列中,清空微队列。

  • mi:[]
  • ma:[setTimeout()1,setTimeout()2]

Step3:先微后宏,微队列清空了接下来就是宏队列。谁先谁上,所以先解决setTimeout()1。在解决setTimeout()1时又发现存在微队列任务。所以先输出 ‘3’ ,将setTimeout()1中的Promise放入微队列中。

  • mi:[Promise]
  • ma:[setTimeout()2]

Step4:先微后宏。先输出 ‘4’ ,后输出 ‘2’ 。队列清空。

  • mi:[]
  • ma:[]

所以结果: ‘1’ ‘3’ ‘4’ ‘2’

/* 实例六(比较经典) */
setTimeout(function(){console.log(4)},0); 

  new Promise(function(resolve){ 
    console.log(1) 
    for( var i=0 ; i<10000 ; i++ ){
       i==9999 && resolve() 
    } 
    console.log(2) 
  }).then(function(){ 
    console.log(5) 
  }); 
  console.log(3);  
  
  //=>   1  2  3  5  4   (自上而下,先同后异,先微后宏)

解释说明:
Step1:自上而下,先同后异,Promise中 executor 函数属于同步任务,所以自上而下执行。注意:输出1以后接着跑1000次循环才调用resolve方法,其实resolve()的意思是把 Promise对象实例的状态从pending变成 fulfilled(即成功),成功的回调就是对应的then方法。仅仅如此而已,所以resolve() 后面的 console.log(2) 会先执行。输出 1 2 3,promise.then进入微队列,setTimeout() 进入宏队列,等待被调用。

  • mi:[Promise.then]
  • ma:[setTimeout()]

Step2:先微后宏,输出 5 再输出 4;情空队列。

  • mi:[]
  • ma:[]

所以结果: 1 2 3 5 4

/* 实例七 */
console.log(1);

setTimeout(() => {                       //1
  console.log(2);
  Promise.resolve().then(() => {         //1
    console.log(3)
  });
});

new Promise((resolve, reject) => {       //2
  console.log(4)
  resolve(5)
}).then((data) => {
  console.log(data);
})

setTimeout(() => {                       //2
  console.log(6);
})

console.log(7);

// => 1  4  7  5  2  3  6  (自上而下,先同后异,先微后宏,谁先谁上)

解释说明:
Step1:自上而下,先同后异。输出1 4 7,Promise2进入微队列,setTimeout()1,setTimeout()2进入宏队列。

  • mi:[Promise2]
  • ma:[setTimeout()1,setTimeout()2]

Step2:先微后宏。先解决微队列任务因为resolve(5)所以输出 5,清空微队列。

  • mi:[]
  • ma:[setTimeout()1,setTimeout()2]

Step3:先微后宏,谁先谁上。后解决宏队列任务所以先解setTimeout()1,但是在此过程中发现存在微队列任务。所以先输出 2 ,Promise1进入微队列。

  • mi:[Promise1]
  • ma:[setTimeout()2]

Step4:先微后宏。先输出 3 ,后输出 6。清空队列。

  • mi:[]
  • ma:[]

所以结果: 1 4 7 5 2 3 6

/* 实例八 */
console.log(1);

setTimeout(() => {  //1
  console.log(2);
  Promise.resolve().then(() => {  //1
    console.log(3)
  });
});

new Promise((resolve, reject) => {  //2
  console.log(4)
  resolve(5)
}).then((data) => {
  console.log(data);
  
  Promise.resolve().then(() => {  //3
    console.log(6)
  }).then(() => {
    console.log(7)
    setTimeout(() => {  //2
      console.log(8)
    }, 0);
    setTimeout(() => {  //3
      console.log(9)
    }, 0);
  });
})

setTimeout(() => {  //4
  console.log(10);
})

console.log(11);

//=>  1  4  11  5  6  7  2  3  10  8  9  (自上而下,先同后异,先微后宏,谁先谁上)

再来分析一波:
Step1:自上而下,先同后异。输出 1 4 11,Promise2进入微队列,setTimeout()1,setTimeout()4进入宏队列。等待被调用。

  • mi:[Promise2]
  • ma:[setTimeout()1,setTimeout()4]

Step2:先微后宏。先解决微队列任务,此时注意遇到执行microtask queue中任务的时候,如果又产生了microtask,那么会继续添加到队列的末尾,也会在这个周期执行,直到microtask queue为空停止。又遇到宏队列任务。所以输出5 6 7,setTimeout()2,setTimeout()3自上而下进入红队列中,清空微队列。

  • mi:[]
  • ma:[setTimeout()1,setTimeout()4,setTimeout()2,setTimeout()3]

Step3:先微后宏,谁先谁上。后解决宏队列任务。解决setTimeout()1发现存在微队列任务。所以输出 2 ,Promise1进入微队列。

  • mi:[Promise1]
  • ma:[setTimeout()4,setTimeout()2,setTimeout()3]

Step4:先微后宏,谁先谁上。先解决微队列任务输出 3,后解决宏队列任务,谁先谁上,依次输出 10 8 9

所以结果:1 4 11 5 6 7 2 3 10 8 9

/* 执行下面这段代码,执行后,在 5s 内点击两下,过一段时间(>5s)后,再点击两下,整个过程的输出结果是什么? */
setTimeout(function(){
    for(var i = 0; i < 100000000; i++){}
    console.log('timer a');
}, 0)

for(var j = 0; j < 5; j++){
    console.log(j);
}

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

function waitFiveSeconds(){
    var now = (new Date()).getTime();
    while(((new Date()).getTime() - now) < 5000){}
    console.log('finished waiting');
}

document.addEventListener('click', function(){
    console.log('click');
})

console.log('click begin');
waitFiveSeconds();

//=>  0  1  2  3  4  'click begin'  'finished waiting'  'click'(<5s)  'timer a'  'timer b'  'click'(>5s)

解释说明:
同步任务结束后,js引擎线程空闲后会查看是否有事件可执行,接着在处理其他异步任务。所以5s内对页面进行点击,同步任务结束后执行,再处理异步任务。5s后对页面进行点击,js引擎线程空闲状态(再无任务可执行)再执行事件。

(4)实例题(重点)(NodeJs)

提个醒:
Timer Queue -> 步骤2 -> IO Queue -> 步骤2 -> Check Queue -> 步骤2 -> Close Callback Queue -> 步骤2 -> Timers Queue …
NodeJsEvent Loop口诀: 自上而下,先同后异,分队列,按步骤,父执行,子等待

/* 实例一 */
console.log('start');

setTimeout(() => {          // 父callback1
  console.log(1);
  setTimeout(() => {        // 子callback2
    console.log(2);
  }, 0);
  setImmediate(() => {      // 子callback3
    console.log();
  })
  process.nextTick(() => {  // 子callback4
    console.log(4);  
  })
}, 0);

setImmediate(() => {        // 父callback5
  console.log(5);
  process.nextTick(() => {  // 子callback6
    console.log(6);  
  })
})

setTimeout(() => {          // 父callback7              
  console.log(7);
  process.nextTick(() => {  // 子callback8
    console.log(8);   
  })
}, 0);

process.nextTick(() => {    // 父callback9
  console.log(9);  
})

console.log('end');

//=>   "start"  "end"  9  1  7  4  8  5  3  6  2

解释说明:
Step1:自上而下,先同后异。输出"start",“end”。
Step2:分队列。

  • timer Queue:[父callback1,父callback7]
  • check Queue:[父callback5]
  • next Tick Queue:[父callback9]

Step3:按步骤。NodeJs Event Loop的开始是微任务。所以开始依次执行微任务Next Tick Queue中的全部回调任务。此时next Tick Queue中只有一个父callback9,将其取出放入调用栈中执行,打印9。清空next Tick Queue。

  • timer Queue:[父callback1,父callback7]
  • check Queue:[父callback5]
  • next Tick Queue:[]

Step4:按步骤。执行第1个阶段timer Queue中的所有任务,先取出父callback1执行,打印1,父callback1函数继续向下,依次把子callback2放入timer Queue中,把子callback3放入check Queue中,把子callback4放入next Tick Queue中,然后父callback1执行完毕。再取出timer Queue中此时排在首位的父callback7执行,打印7,把子callback8放入next Tick Queue中,执行完毕。注意: 若父子同属一个任务队列,则父执行,子不执行(等待)!!!比如: 父callback1与子callback2。

  • timer Queue:[子callback2]
  • check Queue:[父callback5,子callback3]
  • next Tick Queue:[子callback4,子callback8]

Step5:按步骤。每阶段的宏任务队列执行完毕后,都会开始执行微任务。
所以输出 4 8。清空微队列任务。

  • timer Queue:[子callback2]
  • check Queue:[父callback5,子callback3]
  • next Tick Queue:[]

Step6:按步骤。下一阶段IO Queue 跳过。到Check Queue阶段,执行Check Queue中的所有任务。取出父callback5执行,打印5,把子callback6放入next Tick Queue中,执行子callback3,打印3。清空check Queue队列。存在疑问?为什么父callback5执行,子callback3也执行?不是说父执行,子不执行吗?原因:子callback3的父是谁?与父callback5有关系吗?半毛钱关系都没有。

  • timer Queue:[子callback2]
  • check Queue:[]
  • next Tick Queue:[子callback6]

Step6:按步骤。每阶段的宏任务队列执行完毕后,都会开始执行微任务。所以先输出 6 ,最后输出 2。清空队列。

  • timer Queue:[]
  • check Queue:[]
  • next Tick Queue:[]

所以结果:“start” “end” 9 1 7 4 8 5 3 6 2

/* 实例二 */
console.log('1');

setTimeout(function() {
    console.log('2');
    process.nextTick(function() {
        console.log('3');
    })
    new Promise(function(resolve) {
        console.log('4');
        resolve();
    }).then(function() {
        console.log('5')
    })
})

new Promise(function(resolve) {
    console.log('7');
    resolve();
}).then(function() {
    console.log('8')
})
process.nextTick(function() {
  console.log('6');
})

setTimeout(function() {
    console.log('9');
    process.nextTick(function() {
        console.log('10');
    })
    new Promise(function(resolve) {
        console.log('11');
        resolve();
    }).then(function() {
        console.log('12')
    })
})

//=> 1  7  6  8  2  4  9 11 3 10  5 12  (比较简单)

3. 异步任务说明

异步:不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。如果在函数A返回的时候,调用者还不能马上得到预期的结果,而是需要在将来通过一定的手段得到,那么这个函数就是异步的 。(比如setTimeout和setlnterval;DOM事件;ES6中的Promise;Ajax异步请求等)

参考文章:
https://segmentfault.com/a/1190000018181334

总结

  1. JavaScript单线程。将所有任务分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。
  2. 同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
  3. 浏览器的Event Loop和NodeJS的Event Loop是不同的,实现机制也不一样,不要混为一谈。
  • 浏览器环境下的Event Loop
    实现机制:自上而下,先同后异,先微后宏,谁先谁上
  • NodeJs环境下的Event Loop
    实现机制:自上而下,先同后异,分队列,按步骤,父执行,子等待
  1. 浏览器可以理解成只有1个宏任务队列和1个微任务队列,先执行全局Script代码,执行完同步代码调用栈清空后,从微任务队列中依次取出所有的任务放入调用栈执行,微任务队列清空后,从宏任务队列中只取位于队首的任务放入调用栈执行,注意这里和Node的区别,只取一个,然后继续执行微队列中的所有任务,再去宏队列取一个,以此构成事件循环。
  2. NodeJS可以理解成有4个宏任务队列和2个微任务队列,但是执行宏任务时有6个阶段。先执行全局Script代码,执行完同步代码调用栈清空后,先从微任务队列Next Tick Queue中依次取出所有的任务放入调用栈中执行,再从微任务队列Other Microtask Queue中依次取出所有的任务放入调用栈中执行。然后开始宏任务的6个阶段,每个阶段都将该宏任务队列中的所有任务都取出来执行(注意,这里和浏览器不一样,浏览器只取一个),每个宏任务阶段执行完毕后,开始执行微任务,再开始执行下一阶段宏任务,以此构成事件循环。
  3. MacroTask包括: setTimeout、setInterval、 setImmediate(Node)、requestAnimation(浏览器)、IO、UI rendering
    Microtask包括: process.nextTick(Node)、Promise、Object.observe、MutationObserver
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值