javaScript浅谈----事件循环(event loop)

126 阅读5分钟

什么是事件?

事件就是js侦测到用户的操作或是页面上的一些行为。

js控制页面的行为是由事件驱动的。

解释:在W3C标准中:事件可以写在行内,但是因为结构和行为要分离,所以我们一半情况下用JS的方法来绑定事件,只有在极少数情况下,才将事件写在行内。事件的绑定方法:浏览器中的节点(节点).on事件名 = function(){ 要干什么?(放在浏览器中,不执行,当事件发生的时候再执行) }

总结:事件就是给浏览器定义一个预处理函数,当事件触发的时候,执行函数,这就是事件。

在JS中常见的事件有?

  • 鼠标点击--------onclick
  • 鼠标双击--------ondblclick
  • 页面或图片加载-----onload
  • 鼠标移入--------onmouseover
  • 鼠标离开--------onmouseout
  • 元素获得焦点------onfocus
  • 元素失去焦点------onblur
  • touchstart:手指触摸到屏幕会触发
  • touchmove:当手指在屏幕上移动时,会触发
  • touchend:当手指离开屏幕时,会触发
  • touchcancel:可由系统进行的触发,比如手指触摸屏幕的时候,突然alert了一下,或者系统中其他打断了touch的行为,则可以触发该事件

谈一谈js的事件循环机制?

解释:由于js是单线程运行,js中的遇到函数调用时会将它压入调用栈中,当函数执行完成后,会从调用栈中弹出。当遇到setTimeout,setInterval,script的时候,这些属于宏任务,会将它压入宏任务队列,当遇到promise.then,process.nextTick等这些微任务时,会将他们压入微任务队列。当同步代码执行完成后,会查看当前的微任务队列是否为空,如果为空,就执行宏任务队列,如果不为空,就执行当前微任务。微任务执行完成后进入下一次事件循环执行当前宏任务,如果当前的宏任务队列中有同步,就执行同步,没有执行微任务,没有微任务,再进行下一次事件循环。

宏任务和微任务有那些?以及怎么执行的?

宏任务:script、setTimeOut、setInterval、setImmediate

微任务:promise.then,process.nextTick、Object.observe、MutationObserver

注意:Promise是同步任务

执行顺序:

1.执行宏任务script,

2.进入script后,所有的同步任务主线程执行

3.所有宏任务放入宏任务执行队列

4.所有微任务放入微任务执行队列

5.先清空微任务队列,

6.再取一个宏任务,执行,再清空微任务队列

7.依次循环

从 event loop 解释,为何微任务执行更早?

  • 微任务是 ES6 语法规定的(被压入 micro task queue)。
  • 宏任务是由浏览器规定的(通过 Web APIs 压入 Callback queue)。
  • 宏任务执行时间一般比较长。
  • 每一次宏任务开始之前一定是伴随着一次 event loop 结束的,而微任务是在一次 event loop 结束前执行的。

异步任务进入任务队列的过程

 setTimeout(() => {
    console.log("a");
  }, 10000);
  setTimeout(() => {
    console.log("b");
 }, 100);

解释:异步任务进入任务队列遵循着“先进先出”的原则,同时由于js是单线程的,但是浏览器不是单线程的,不同的浏览器线程对应处理不同的事件。当对应的事件可以执行的时候,对应的线程把把对应的事件推入到事件队列中,这也就解释了为什么第二个setTimeout先被打印。

对应的线程

  • js引擎线程:用于解释执行js代码、用户输入、网络请求等;
  • GUI渲染线程:绘制用户界面,与JS主线程互斥(因为js可以操作DOM,进而会影响到GUI的渲染结果);
  • http异步网络请求线程:处理用户的get、post等请求,等返回结果后将回调函数推入到任务队列;
  • 定时触发器线程:setInterval、setTimeout等待时间结束后,会把执行函数推入任务队列中;
  • 浏览器事件处理线程:将click、mouse等UI交互事件发生后,将要执行的回调函数放入到事件队列中。

案例一:

 setTimeout(function () {
    console.log("1");
  });
  new Promise(function (resolve) {
    console.log("2");
    resolve();
  }).then(function () {
    console.log("3");
  });
  console.log("4");
  new Promise(function (resolve) {
    console.log("5");
    resolve();
  }).then(function () {
    console.log("6");
  });
  setTimeout(function () {
    console.log("7");
  });
  function bar() {
    console.log("8");
    foo();
  }
  function foo() {
    console.log("9");
  }
  console.log("10");
  bar();
  //  2,4,5,  10,8,9,   3,6,1,7

解析:

1.首先浏览器执行Js代码由上至下顺序,遇到setTimeout,把setTimeout分发到宏任务Event Queue

2.new Promise属于主线程任务直接执行打印2

3.Promise下的then方法属于微任务,把then分到微任务 Event Queue

4.console.log(‘4’)属于主线程任务,直接执行打印4

5.又遇到new Promise也是直接执行打印5,Promise 下到then分发到微任务Event Queue

6.又遇到setTimouse也是直接分发到宏任务Event Queue中,等待执行

7.console.log(‘10’)属于主线程任务直接执行

8.遇到bar()函数调用,执行构造函数内到代码,打印8,在bar函数中调用foo函数,执行foo函数到中代码,打印9

9.主线程中任务执行完后,就要执行分发到微任务Event Queue中代码,实行先进先出,所以依次打印3,6

10.微任务Event Queue中代码执行完,就执行宏任务Event Queue中代码,也是先进先出,依次打印1,7。

案例二:

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