admin管理员组

文章数量:1530085

目录

1. 事件轮询机制(Event Loop)是什么

1.1 宏任务、微任务

1.2 Event Loop 循环过程

1.3 经典题目分析

1.3.1 第一轮事件循环

1.3.2 第二、三次事件循环

1.3.3 参考文章

2. async、await 在事件轮询中的执行时机

3. 浏览器更新渲染时机、Vue nextTick

3.1 Event Loop 与 浏览器更新渲染时机

3.2 Vue 为什么要优先使用微任务实现 nextTick

4. Node.js 中的 process.nextTick

4.1 什么是 process.nextTick

4.2 浏览器事件循环 VS  Node 事件循环

5. setTimeout VS setInterval

5.1 setTimeout / setInterval 的执行时间为啥无法确定呢?

5.2 setTimeout / setInterval 概念容易踩得坑

5.2.1 setTimeout 举个栗子~

5.2.2 setInterval 举个栗子~

5.2.3 setTimeout 模拟实现 setInterval

5.2.4 setInterval 模拟实现 setTimeout

5.3 requestAnimationFrame 浏览器提供的动画 API

5.3.1 setTimeout / setInterval 动画卡顿

5.3.2 什么是 requestAnimationFrame

5.3.3 setTimeout VS setInterval VS requestAnimationFrame

5.3.4 参考文章


HTML5 中的 WebWoker,号称 JavaScript 多线程版

其实本质上还是用 JavaScript 单线程模拟出来的,所以搞明白 Event Loop 很重要

 

1. 事件轮询机制(Event Loop)是什么

1.1 宏任务、微任务

任务可以分成两种:

  • 宏任务:在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行下一个任务
  • 微任务:不进入主线程、而进入"微任务列表" 的任务

宏任务(Macrotasks)包括:

  • script 全部代码(注意:同步代码也属于宏任务)
  • setTimeout
  • setInterval
  • setImmediate

微任务(Microtasks)包括:

  • Promise
  • MutationObserver

不同类型的任务,会进入对应的 Event Queue,比如 setTimeout 和 setInterval 会进入相同的 Event Queue

1.2 Event Loop 循环过程

JavaScript 是单线程的,同一时间只能做一件事。所有任务都需要排队,前一个任务结束,才会执行后一个任务(JavaScript 是一门单线程语言,Event Loop 是 JavaScript 的执行机制)

  • 宏任务是一个个执行;
  • 如果执行过程中,遇到微任务,就把他加入微任务队列;
  • 当前宏任务执行完后,会判断 微任务列表 中是否有任务;
  • 如果有,就把该微任务放到主线程中执行;如果没有,就继续执行下一个宏任务,不断循环;

用 Ajax 举个栗子:

  • Ajax 作为微任务,进入微任务列表,注册回调函数 success
  • 执行同步代码,console.log('代码执行结束')
  • 等待 Ajax 请求完成,主线程从 微任务列表 读取回调函数 success 并执行
let data = [];
$.ajax({
    url:www.javascript,
    data:data,
    success:() => {
        console.log('发送成功!');
    }
})
console.log('代码执行结束');

1.3 经典题目分析

Promise.resolve()
  .then(function() {
    console.log("promise0"); // 2 执行微任务1 Promise1
  })
  .then(function() {
    console.log("promise5"); // 4 执行微任务3 Promise3
  });

setTimeout(() => {
  console.log("timer1"); // 5 执行宏任务 setTimeout1
  Promise.resolve().then(function() {
    console.log("promise2"); // 6 执行宏任务中的微任务
  });
  Promise.resolve().then(function() {
    console.log("promise4"); // 7 执行宏任务中的微任务
  });
}, 0);

setTimeout(() => {
  console.log("timer2"); // 8 执行宏任务 setTimeout2
  Promise.resolve().then(function() {
    console.log("promise3"); // 9 执行宏任务中的微任务
  });
}, 0);

Promise.resolve().then(function() {
  console.log("promise1"); // 3 执行微任务2 Promise2
});

console.log("start"); // 1 执行同步代码

打印结果: start ------ promise0 ------ promise1 ------ promise5 ------ timer1 ------ promise2 ------ promise4 ------ timer2 ------ promise3

 

1.3.1 第一轮事件循环

  • 整体 script 作为第一个宏任务进入主线程,遇到 console.log,输出 start
  • 遇到 Promise,其回调函数 then 被分发到 微任务队列 中。记为 Promise1
  • 遇到 Promise,其回调函数 then 被分发到 微任务队列 中。记为 Promise2
  • 遇到 setTimeout,分发到 宏任务队列。记为 setTimeout1
  • 遇到 setTimeout,分发到 宏任务队列。记为 setTimeout2
  • 遇到 Promise,其回调函数 then 被分发到 微任务队列 中。记为 Promise3

宏任务队列:

  • setTimeout1
  • setTimeout2

微任务队列:

  • Promise1
  • Promise2
  • Promise3

执行同步代码,输出 start

执行微任务队列,输出 promise0 ------ promise1 ------ promise5

第 2、3、4 步的输出可能会有疑问

这里举个栗子~

new Promise(function(resolve) {
    // 这里作为同步代码,立即执行
    console.log('promise');
}).then(function() {
    // then 中的代码,会被放入微任务队列
    console.log('then');
})

可以看出:

  • new Promise 中的代码,是立即执行的
  • 而回调函数 then,会被放到微任务队列中

1.3.2 第二、三次事件循环

第二次事件循环:

  • 执行第二个宏任务 setTimeout1
  • 先执行同步代码,输出 timer1
  • 再将微任务放到微任务列表中,由于主线程没有代码可执行,所以将微任务放到主任务线程中,输出 promise2 ------ promise4

第三次事件循环:

  • 执行第三个宏任务 setTimeout2
  • 先执行同步代码,输出 timer2
  • 再将微任务放到微任务列表中,由于主线程没有代码可执行,所以将微任务放到主任务线程中,输出 promise3

1.3.3 参考文章

这篇文章的示例讲解比较清楚,值得学习

这一次,彻底弄懂 JavaScript 执行机制 - 掘金本文的目的就是要保证你彻底弄懂javascript的执行机制,如果读完本文还不懂,可以揍我。 不论你是javascript新手还是老鸟,不论是面试求职,还是日常开发工作,我们经常会遇到这样的情况:给定的几行代码,我们需要知道其输出内容和顺序。因为javascript是一门单线程…https://juejin/post/6844903512845860872

2. async、await 在事件轮询中的执行时机

async 隐式返回 Promise,会产生一个微任务

await 后面的代码,在执行微任务时执行

举个栗子 ~~ 看下面的代码

console.log("script start"); // 1 同步代码

async function async1() {
  await async2(); // 2 同步代码
  console.log("async1 end"); // 5 这里的执行时机:在执行微任务时执行
}

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

async1();

setTimeout(function() {
  console.log("setTimeout"); // 8 第二个宏任务
}, 0);

new Promise(resolve => {
  console.log("Promise"); // 3 同步代码
  resolve();
})
  .then(function() {
    console.log("promise1"); // 6 微任务
  })
  .then(function() {
    console.log("promise2"); // 7 微任务
  }); 

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

打印结果: script start ------ async2 end ------ Promise ------ script end ------ async1 end ------ promise1 ------ promise2 ------ setTimeout

第一次事件循环:

  • 整体 script 作为第一个宏任务进入主线程,遇到 console.log,输出  script start
  • 执行 async1,里面的 await async2 是个同步代码,所以打印了 async2 end;同时,async1 还产生一个微任务(就是 await 后面的代码),放进微任务列表,暂时不执行
  • 继续执行主任务,走到 promise,走里面的同步代码,打印了 Promise,里面的 then 放进微任务列表,暂时不执行
  • 继续执行主任务,执行同步代码 console.log...,打印了 script end
  • 此时微任务列表里,首个微任务是 await 后面的代码,打印了 async1 end;剩下两个微任务是 promise 里的,打印了 promise1、promise2
  • 第一个宏任务执行完毕

第二次事件循环:

  • 执行第二个宏任务 setTimeout,打印了 setTimeout

3. 浏览器更新渲染时机、Vue nextTick

3.1 Event Loop 与 浏览器更新渲染时机

浏览器更新渲染,会在 Event Loop 中的 宏任务、微任务 完成后进行

先宏任务,再微任务,然后再渲染更新

宏任务队列中,如果有大量任务等待执行时,应该将 修改 DOM 作为微任务,这样就能在 本次事件轮询中 更新 DOM(Vue nextTick 就是这么实现的),更快的将 页面变化 呈现给用户

从event loop规范探究javaScript异步及浏览器更新渲染时机 · Issue #5 · aooy/blog · GitHub作者:杨敬卓 转载请注明出处 异步的思考 event loops隐藏得比较深,很多人对它很陌生。但提起异步,相信每个人都知道。异步背后的“靠山”就是event loops。这里的异步准确的说应该叫浏览器的event loops或者说是javaScript运行环境的event loops,因为ECMAScript中没有event loops,event loops是在HTML Standard定义的。 event loops规范中定义了浏览器何时进行渲染更新,了解它有助于...https://github/aooy/blog/issues/5

3.2 Vue 为什么要优先使用微任务实现 nextTick

Vue3 nextTick 源码地址:core\packages\runtime-core\src\scheduler.ts

Vue nextTick 的码的优先级判断:Promise > MutationObserver > setImmediate > setTimeout

答:根据 Event Loop 与浏览器更新渲染时机可得:

  • 优先使用 Promise 等微任务,本次事件轮询就能获得更新的 DOM
  • 如果使用宏任务,要到下一次事件轮询中,才能获得更新的 DOM

学习Vue3 第三十五章(Evnet Loop 和 nextTick)_小满zs的博客-CSDN博客在我们学习nextTick 之前需要先了解Event Loop 时间循环机制在我们学js 的时候都知道js 是单线程的如果是多线程的话会引发一个问题在同一时间同时操作DOM 一个增加一个删除JS就不知道到底要干嘛了,所以这个语言是单线程的但是随着HTML5到来js也支持了多线程webWorker 但是也是不允许操作DOM单线程就意味着所有的任务都需要排队,后面的任务需要等前面的任务执行完才能执行,如果前面的任务耗时过长,后面的任务就需要一直等,一些从用户角度上不需要等待的任务就会一直等待,这个从体验角度上来https://xiaoman.blog.csdn/article/details/125237755https://github/qingzhou729/study/issues/15https://github/qingzhou729/study/issues/15

4. Node.js 中的 process.nextTick

4.1 什么是 process.nextTick

process.nextTick 相当于 Node.js 中的 setTimeout,是 Node.js 定义的一种机制,它有自己的 nextTickQueue

process.nextTick 不能完全当作 JavaScript 中的微任务,process.nextTick 执行顺序早于微任务

举个栗子 ~~ 看下面的代码:

console.log("start"); // 1 同步代码

setTimeout(() => {
  console.log("setTimeout"); // 6 第二个宏任务
}, 0);

Promise.resolve().then(() => {
  // 4 第一个微任务,早于 process.nextTick 内部的 promise 放入微任务列表
  console.log("promise");
});

process.nextTick(() => {
  console.log("nextTick"); // 3 Node.js process.nextTick 早于微任务执行
  Promise.resolve().then(() => {
    console.log("promise1"); // 5 第二个微任务
  });
});

console.log("end"); // 2 同步代码

执行结果:start ------ end ------ nextTick ------ promise ------ promise1 ------ setTimeout

4.2 浏览器事件循环 VS  Node 事件循环

浏览器与Node的事件循环(Event Loop)有何区别? - 知乎摘要: 浏览器和Node中Event Loop其实是不相同的。前言本文我们将会介绍 JS 实现异步的原理,并且了解了在浏览器和 Node 中 Event Loop 其实是不相同的。 一、线程与进程1. 概念我们经常说 JS 是单线程执行的,指…https://zhuanlan.zhihu/p/54882306

5. setTimeout VS setInterval

  • setTimeout 固定时长后执行
  • setInterval 间隔固定时间后重复执行

5.1 setTimeout / setInterval 的执行时间为啥无法确定呢?

举个栗子~ 

定时器内部代码,需要进行大量计算,或者 DOM 操作,代码执行时间 超过了 定时器设置的时间

再举个栗子~

setTimeout 早早的把 task() 放进等待队列了,但是由于 JavaScript 是单线程的,只能等 sleep() 执行完毕(时间远超 3s),才能执行 task()

setTimeout(() => {
    // 控制台执行 task() 需要的时间,远远超过 3 秒
    task()
}, 3000)

sleep(10000000)

5.2 setTimeout / setInterval 概念容易踩得坑

5.2.1 setTimeout 举个栗子~

setTimeout(fn, 0),可以立即执行吗?答案是不行。

  • setTimeout 指的是:主线程的任务都执行完了,再等 x 秒,才能执行内部代码;
  • 即使主线程为空,也不能立即执行,因为 JavaScript 规定 setTimeout、setInterval 最短时长为 4ms;

5.2.2 setInterval 举个栗子~

对于 setInterval(fn, ms) 来说,他是每过 ms 秒就一定会执行一次 fn 吗?答案是不是。

  • 是每过 ms 秒,会创建一个 fn 进入任务队列;
  • 一旦 setInterval 的回调函数 fn 实际执行时间,超过了延迟时间 ms,那么就完全看不出来有时间间隔了;

5.2.3 setTimeout 模拟实现 setInterval

// 使用闭包实现
function mySetInterval(fn, t) {
  let timer = null;
  function interval() {
    fn();
    timer = setTimeout(interval, t);
  }
  interval();
  return {
    // cancel用来清除定时器
    cancel() {
      clearTimeout(timer);
    }
  };
}

5.2.4 setInterval 模拟实现 setTimeout

function mySetTimeout(fn, time) {
  let timer = setInterval(() => {
    clearInterval(timer);
    fn();
  }, time);
}

// 使用
mySetTimeout(() => {
  console.log(1);
}, 2000);

 

5.3 requestAnimationFrame 浏览器提供的动画 API

5.3.1 setTimeout / setInterval 动画卡顿

setTimeout / setInterval 通过设置一个间隔时间,不断改变 css 实现动画效果,在不同设备上可能会出现卡顿、抖动等现象,这是因为:

  • 不同设备的屏幕,刷新频率可能不同
  • setTimeout / setInterval 只能设置固定的时间间隔,这个时间 与 屏幕刷新间隔可能不同

5.3.2 什么是 requestAnimationFrame

HTML5 新增 API,类似于 setTimeout,window.requestAnimationFrame

requestAnimationFrame 告诉浏览器,在下次重绘之前,执行传入的回调函数(通常是操作 DOM,更新动画的函数),也就是说:刷新频率与显示器的刷新频率保持一致

requestAnimationFrame 是浏览器专门为 动画(DOM 动画、Canvas 动画、 SVG 动画、WebGL 动画) 提供的API,使用该 API 可避免使用setTimeout / setInterval 造成的动画卡顿

5.3.3 setTimeout VS setInterval VS requestAnimationFrame

引擎层面的区别:

  • setTimeout / setInterval 属于 JavaScript 引擎,存在事件轮询
  • requestAnimationFrame 属于 GUI 引擎

JavaScript 引擎与 GUI 引擎是互斥的,也就是说:

  • GUI 引擎在渲染时,会阻塞 JavaScript 引擎的计算;
  • 如果在 GUI 渲染的时候,JavaScript 又同时改变了DOM,就会造成页面渲染不同步;

性能层面的区别:

  • 当页面被隐藏或最小化时,定时器 setTimeout 仍会在后台运行,执行动画任务
  • 当页面被隐藏或最小化时,屏幕刷新任务会被系统暂停,requestAnimationFrame 也会停止

5.3.4 参考文章

setTimeout/setInterval与requestAnimationFrame的区别?_尹成诺的博客-CSDN博客_settimeout setinter requestanmiationfarme提到 setTimeout/setInterval 以及 requestAnimationFrame,大家的第一反应是动画相关的两个 API。什么是web 动画我们来谈谈什么是动画。动画其实是一种假象,是一种不连续的运动以帧的形式呈现给我 们的东西。在二十世纪,通常人们观看的电影其实就是通过胶片记录和投影的。它是以每 秒至少 24 帧的速度形成的视觉上的运动起来的假象。NTSC 广播的标准...https://blog.csdn/weixin_40851188/article/details/89669416

本文标签: 时机浏览器机制事件Loop