宏任务与微任务和浏览器的事件循环机制

Browser-Event-Loop

宏任务和微任务

宏任务包括:script(整体代码),I/O, setTimeout,setInterval,requestAnimationFrame,setImmediate。

其中setImmediate只存在于Node中,requestAnimationFrame只存在于浏览器中。

微任务包括: Promise,Object.observe(已废弃),MutationObserver(html5新特性),process.nextTick。

其中process.nextTick只存在于Node中,MutationObserver只存在于浏览器中。

UI Rendering不属于宏任务,也不属于微任务,它是一个与微任务平行的一个操作步骤

事件环分析练习

例1

// 设置trap
const bodyStyle = new Proxy(document.body.style, {
  set(target, property, value, receiver) {
    switch(property) {
      case "backgroundColor":
        target.backgroundColor = value;
        console.log(`设置了颜色-${value}`);
        break;
      default:
        break;
    }
    return true;
  }
});

bodyStyle.backgroundColor = "orange";
console.log(1);

setTimeout(() => {
  bodyStyle.backgroundColor = "green";
  console.log(2);
}, 100);

Promise.resolve(3).then(num => {
  bodyStyle.backgroundColor = "purple";
  console.log(num);
});

console.log(4);
// 输出结果
// 设置了颜色-orange
// 1
// 4
// 设置了颜色-purple
// 3
// 设置了颜色-green
// 2

// (orange背景并没有渲染,因为在执行本轮微任务时被覆盖为了purple,微任务后进行UI渲染)

例2

Promise.resolve().then(() => {
  console.log("p1");
  setTimeout(() => {
    console.log("s2");
  }, 0);
});

setTimeout(() => {
  console.log("s1");
  Promise.resolve().then(() => {
    console.log("p2");
  });
}, 0);

// 第一圈:p1
// 第二圈:s1 p2
// 第三圈:s2
Promise.resolve().then(() => {
  console.log("p1");
  setTimeout(() => {
    console.log("s2");
  }, 0);
  setTimeout(() => {
    console.log("s3");
  }, 0);
});

setTimeout(() => {
  console.log("s1");
  Promise.resolve().then(() => {
    console.log("p2-1");
  }).then(() => {
    console.log("p2-2");
  });
}, 0);

// 第一圈:p1
// 第二圈:s1 p2-1 p2-2
// 第三圈:s2
// 第四圈:s3

例3

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

new Promise((resolve, reject) => {
  console.log(3);
  resolve("");
  console.log(4);
}).then((res) => {
  console.log(5);
});
console.log(6);

// 1 3 4 6 
// 5
// 2
 
//            异步任务             异步任务
// 同步代码 -> 微任务代码 -> 渲染 -> 宏任务代码
console.log(1);
setTimeout(() => {
  Promise.resolve().then(() => {
    console.log("p2");
  })
}, 10); // 10ms延迟

new Promise((resolve, reject) => {
  console.log(3);
  resolve("");
  console.log(4);
}).then(() => {
  setTimeout(() => {
    console.log("p1");
  }, 0);
});
console.log(6);

// 1 3 4 6
// p1
// p2

例4

let res = function() {
  console.log(1);
  return new Promise((resolve, reject) => {
    console.log(2);
    resolve(4);
  });
}

new Promise(async (resolve, reject) => {
  console.log(3);
  let test = await res();
  console.log(test);
});

console.log(5);

new Promise((resolve, reject) => {
  console.log(6);
});

console.log(7);

// 同步任务:3 1 2 5 6 7
// 微任务:4
let res = function() {
  console.log(1);
  return new Promise((resolve, reject) => {
    // setTimeout2
    setTimeout(() => {
      // p3
      new Promise((resolve) => {
        console.log(2);
        // setTimeout5
        setTimeout(() => {
          console.log(3);
        }, 0);
      });
    }, 0);
    resolve(5);
  });
}

new Promise(async (resolve, reject) => {
  // setTimeout1
  setTimeout(() => {
    // p2
    Promise.resolve().then(() => {
      console.log(4);
    });
  }, 0);
  // p1
  let test = await res();
  console.log(test);
});

// setTimeout3
setTimeout(() => {
  console.log(6);
}, 0);

new Promise((resolve, reject) => {
  // setTimeout4
  setTimeout(() => {
    console.log(7);
  }, 0);
});

console.log(8);
// 1 8 5 4 2 6 7 3
// 同步执行:1 8 清空微任务 > p1:5
// setTimeout1 清空微任务 > p2:4
// setTimeout2:2
// setTimeout3:6
// setTimeout4:7
// setTimeout5:3

例5

const btn = document.getElementById("btn");
// 宏任务:事件处理函数的回调
btn.addEventListener("click", () => {
  console.log(1);
  Promise.resolve("m1").then((str) => {
    console.log(str);
  });
}, false);

btn.addEventListener("click", () => {
  console.log(2);
  Promise.resolve("m2").then((str) => {
    console.log(str);
  });
}, false);

btn.click();
// 1 2
// m1 m2

// 当用户手动点击click按钮的时候,输出结果
// 1 m1
// 2 m2

用户手动点击时进行了两次事件循环,而程序调用方法btn.click()时等同于执行下面的代码。

const handler1 = () => {
  console.log(1);
  Promise.resolve("m1").then(str => {
    console.log(str);
  });
}
const handler2 = () => {
  console.log(2);
  Promise.resolve("m2").then(str => {
    console.log(str);
  });
}
// handler1,handle2的执行是同步代码,同步代码执行完后清空两个微任务
handler1();
handler2();
// 1 2
// m1 m2
const btn = document.getElementById("btn");
btn.addEventListener("click", () => {
  setTimeout(() => {
    console.log(1);
  });

  Promise.resolve("m1").then((str) => {
    console.log(str);
  });
}, false);

btn.addEventListener("click", () => {
  setTimeout(() => {
    console.log(2);
  });

  Promise.resolve("m2").then((str) => {
    console.log(str);
  });
}, false);

btn.click();
// m1 m2
// 1 2

例6

console.log("start");
const interval = setInterval(() => {
  console.log("setInterval");
}, 0);

setTimeout(() => {
  console.log("setTimeout1");
  Promise.resolve()
    .then(() => {
      console.log("p3");
    })
    .then(() => {
      console.log("p4");
    })
    .then(() => {
      setTimeout(() => {
        console.log("setTimeout2");
        Promise.resolve()
          .then(() => {
            console.log("p5");
          })
          .then(() => {
            console.log("p6");
          })
          .then(() => {
            clearInterval(interval);
          });
      }, 0);
    });
}, 0);

Promise.resolve()
  .then(() => {
    console.log("p1");
  })
  .then(() => {
    console.log("p2");
  });

// start p1 p2
// setInterval
// setTimeout1 p3 p4
// setInterval
// setTimeout2 p5 p6

// 更新后的chrome,没有了setInterval的输出
//(客观感受是setInterval的优先级低于了setTimeout,导致还没输出就已经被清除了)
// start p1 p2
// setTimeout1 p3 p4
// setTimeout2 p5 p6

例7

setTimeout(() => {
	console.log('setTimeout1');
  setTimeout(() => {
  	console.log('setTimeout3');
  }, 1000);
  Promise.resolve().then(() => {
  	console.log('then3');
  });
}, 1000);
Promise.resolve().then(() => {
	console.log('then1');
  console.log('then4');
  Promise.resolve().then(() => console.log('then6'));
});
Promise.resolve().then(() => {
	console.log('then2');
  console.log('then5');
  setTimeout(() => {
  	console.log('setTimeout2');
  }, 1000);
});

// then1 then4 then2 then5 then6 本轮循环产生的微任务都会在本次循环清空
// setTimeout1 then3
// setTimeout2
// setTimeout3

例8

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

new Promise((resolve) => {
  console.log(2);
  resolve();
}).then(() => {
  console.log(3);
}).then(() => {
  console.log(4);
});

console.log(6);

// 2 6 3 4
// 1

例9

console.log(1);
setTimeout(() => {
	console.log(2);
  new Promise((resolve) => {
    console.log(3);
    resolve();
  }).then(() => {
  	console.log(4);
  });
});
new Promise((resolve) => {
	console.log(5);
  resolve();
}).then(() => {
  console.log(6);
})
setTimeout(() => {
  console.log(7);
});
setTimeout(() => {
  console.log(8);
  new Promise((resolve) => {
  	console.log(9);
    resolve();
  }).then(() => {
  	console.log(10);
  });
});
new Promise((resolve) => {
  console.log(11);
  resolve();
}).then(() => {
  console.log(12);
});
console.log(13);

// 1 5 11 13 6 12
// 2 3 4
// 7
// 8 9 10

例10

async function async1 () {
  console.log("a1-start");
  await async2();
  console.log("a1-end");
}

async function async2 () {
  console.log("async2");
}

console.log("start");

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

async1();

new Promise((resolve) => {
	console.log("p1");
  resolve();
}).then(() => {
  console.log("p2");
});

console.log("end");

// start a1-start async2 p1 end a1-end p2
// setTimeout
async function async1 () {
  console.log("a1-start");
  await async2();
  console.log("a1-end");
}

async function async2 () {
	new Promise((resolve) => {
    console.log("p1");
    resolve();
  }).then(() => {
    console.log("p2");
  });
}

console.log("start");

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

async1();

new Promise((resolve) => {
	console.log("p3");
  resolve();
}).then(() => {
  console.log("p4");
});

console.log("end");

// start a1-start p1 p3 end p2 a1-end p4
// setTimeout
async function async1 () {
  console.log("a1-start");
  await async2();
  /**
   * awaitPromsie
   * async2().then(() => {
   * 	setTimeout(() => {
   *   console.log("setTimeout1");
   *  })
   * }) 
   */
  setTimeout(() => {
    console.log("setTimeout1");
  });
}

async function async2 () {
  setTimeout(() => {
    console.log("setTimeout2");
  }, 0);
}

console.log("start");

setTimeout(() => {
  console.log("setTimeout3");
}, 0);

async1();

new Promise((resolve) => {
	console.log("p1");
  resolve();
}).then(() => {
  console.log("p2");
});

console.log("end");

// start a1-start p1 end p2
// setTimeout3
// setTimeout2
// setTimeout1

例11

var promise = new Promise((resolve) => {
  console.log(1);
  resolve();
});

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

promise.then(() => {
  console.log(3);
});

var promise2 = getPromise();

async function getPromise () {
  console.log(5);
  await promise;
  console.log(6);
}

console.log(8);

// 1 5 8 3 6
// 2

例12

const LazyMan = function (name) {
  console.log(`Hi i am ${ name }`);
  
  function _eat (food) {
    console.log(`I am eating ${ food }`);
  }
  
  const callbacks = [];
  
  class F {
    sleep (timeout) {
      setTimeout(() => {
        console.log(`等待了${ timeout }秒...`);
        callbacks.forEach(cb => cb());
      }, timeout * 1000);
      return this;
    }
    
    eat (food) {
      callbacks.push(_eat.bind(null, food));
      return this;
    }
  }
  
  return new F();
}
LazyMan("Tony").sleep(5).eat("lunch").eat("fish");

// Hi i am Tony
// 等待了5秒... I am eating lunch  I am eating lunch

参考

知乎-如何解释EventLoop面试官才满意?
mdn-并发模型与事件循环

> cd ..