在实例中重学JavaScript事件循环


Posted in Javascript onDecember 03, 2020

单线程的JS

众所周知js是一门单线程语言,即同一时间只能做一件事。为什么js是单线程的呢,主要与它的用途有关。

作为浏览器脚本语言,js的主要用途是和用户互动&操作DOM,我们并不想并行的操作DOM。如果不是单线程的话,我们一个线程在给DOM节点上添加内容,另一个线程却删除了这个节点,到底该以哪个为准呢?

所以为了避免复杂性,从一诞生,JavaScript 就是单线程。

事件循环(event loop)

JS是一门单线程语言,意味着代码要一行一行的执行。所有任务都要排队,前一个任务结束,才会执行后一个任务。

但平时大家开发时常用到的ajax,setTimeOut,promise之类的并没有阻塞进程。如果浏览器只有一个js引擎构成,遇到上面这些比较耗时的请求或操作时,浏览器就会阻塞住,这肯定不是我们想要的。

其实js单线程是指浏览器在解释和执行js代码时只有一个线程,即js引擎线程。但浏览器还包括一些其他的线程来处理这些异步的方法,比如Web APIs线程,GUI渲染线程等。

事件循环的处理流程:

JS线程依靠调用栈来处理执行js代码,当遇到一些异步的操作时,则将其移交给Web APIs,自己继续往下进行。
Web APIs线程则将收到的事件按一定的规则和顺序添加到任务队列里去。
JS线程处理完当前所有任务(即执行栈为空),则去检查任务队列里是否有等待被处理的事件,若有,则取出一个事件回调放入执行栈中执行。
然后不断循环第三步。

宏任务与微任务

任务队列又分为宏任务队列和微任务队列:

  • 宏任务队列(macrotask queue):存放的是setTimeout, setInterval, setImmediate, I/O, UI rendering等。
  • 微任务队列(microtask queue):存放的是Promises, Object.observe, MutationObserver,process.nextTick等。

所以我们细化一下事件循环的处理流程(浏览器环境):

JS线程依靠调用栈来处理执行js代码,当遇到一些异步的操作时,则将其移交给Web APIs,自己继续往下进行。
Web APIs线程则将收到的事件按一定的规则和顺序添加到任务队列里去。宏任务事件则添加到宏任务队列,微任务事件则添加到微任务队列。
JS线程处理完当前所有任务(即执行栈为空),会先去微任务队列检查是否有待处理的事件,若有,会将微任务队列里的所有事件一件件执行完直到微任务队列为空,再去宏任务队列取出最前面的一个事件执行,执行完这一个宏任务事件后再去检查微任务队列是否有事件待处理。
然后不断循环第三步。

实际需求中重学JavaScript事件循环

什么是JS事件循环?

在秋招的时候经常会被问到这个问题,但自己的理解仅限于以上,然后刷过几道输出值顺序的题目,没有过业务中的实际应用场景。后来拿到offer后就忘的一干二净了,直到毕业入职后开始写代码重新遇到了这才有了更深一步的理解。

背景
用户上传多张图片,前端拿到每张图片的url和宽高发送给后端。

解决
首先是拿到用户上传的文件,做一些校验和限制

// 调用系统弹框添加图片的方法
addFile(e) {
 let uploadFiles = e.target.files,self = this;

 self.getListData = []; // 要传给后端的对象数组

 self.testFiles(uploadFiles) // 对用户上传的文件做一些校验和限制
  
 self.loadAll(uploadiles) // 调用loadAll方法
},

然后让我们看一下loadAll,主要是遍历上传的这些图片文件,然后每一个图片文件都调用了loadImg

loadAll(files) {
 let promises = [],self = this
 
 // 遍历文件流
 files.forEach((file,i) => {
  // 创建对象,push到数组里
  (self.getListData).push({
   imageUrl: '', 
  });
  
  let eachPromise = self.loadImg(file,i)
  // 存储当前promise对象
  promises.push(eachPromise)
 })
 
 Promise.all(promises).then(() => {
  //全部完成,向后端发送请求
 }).catch(err => {
  console.log(err)
 })
},

然后让我们看一下loadImg,这个方法返回一个Promise对象,主要是为了保证拿到图片的URL以及在img.onload里拿到图片的宽高,因为这两个事件都是异步事件。

实际上js主线程是不会等待这两个结果,就会继续往下执行的。但因为我们在img.onload里才会把Promise给resolve出去,而loadAll方法里用了一个Promise.all来等待所有promise都完成,这样就可以保证向后端发送请求时所有的图片的url和宽高都能拿到。

loadImg(file,i) {
 return new Promise(async (resolve,reject) => {
  let self = this
  // 调用公司wos服务,拿图片文件的url
  let successRes = await _fileUpload.uploadFile(item)
  if(successRes && successRes !== 'error'){
    self.getListData[i]['imageUrl'] = successRes.url
  }
  let img = new Image()
  img.src = successRes.url
  img.onload = () => {
   self.getListData[i]['width'] = img.width
   self.getListData[i]['height'] = img.height
   resolve()
  }
  img.onerror = (e) => {
   reject(e)
  }
 })
}

让我们想一下如果把loadImg里拿图片的url这个操作放到loadAll里呢?

loadAll(files) {
 let promises = [],self = this
 
 // 遍历文件流
 files.forEach(async(file,i) => {
  // 创建对象,push到数组里
  (self.getListData).push({
   imageUrl: '', 
  });
  
  // 调用公司wos服务,拿图片文件的url
  let successRes = await _fileUpload.uploadFile(item)
  if(successRes && successRes !== 'error'){
    self.getListData[i]['imageUrl'] = successRes.url
  }
  
  let eachPromise = self.loadImg(file,i)
  // 存储当前promise对象
  promises.push(eachPromise)
 })
 
 Promise.all(promises).then(() => {
  //全部完成,向后端发送请求
 }).catch(err => {
  console.log(err)
 })
},

如果变成这种写法,因为js的主线程执行栈不会等待await返回结果,循环里await _fileUpload.uploadFile(item)这行代码后面的内容会被交给Web APIs然后跳出async函数。继续执行主线程,而现在Promise.all的参数是一个空数组,然后就直接发了请求。但现在并没有拿到图片的URL和宽高。

关键字await只能使async函数一直等待,执行栈当然不可能停下来等待的,await将其后面的内容包装成Promise交给Web APIs后,执行栈会跳出async函数继续执行,直到Promise执行完并返回结果。await只在async函数里面奏效。

总结

从上面这个需求的实现中,好像对事件循环的理解更深刻了!像Promise.then里和await后面的代码都会等待返回结果后再被放入对应事件的任务队列中等待执行,JS线程会继续向下执行调用栈。包括vue中的watch handler也是被先放入了任务队列里等待。

所以可知事件循环在实际工作中对写代码和优化代码都非常重要~如理解有误请在评论区多多指教。

以上就是在实例中重学JavaScript事件循环的详细内容,更多关于JavaScript 事件循环的资料请关注三水点靠木其它相关文章!

Javascript 相关文章推荐
关于js中window.location.href,location.href,parent.location.href,top.location.href的用法与区别
Oct 18 Javascript
用JSON做数据传输格式中的一些问题总结
Dec 21 Javascript
jQuery 联动日历实现代码
May 31 Javascript
JavaScript中的eval()函数详解
Aug 22 Javascript
js跨浏览器实现将字符串转化为xml对象的方法
Sep 25 Javascript
jQuery的deferred对象详解
Nov 12 Javascript
jQuery+HTML5美女瀑布流布局实现方法
Sep 21 Javascript
基于jQuery实现发送短信验证码后的倒计时功能(无视页面关闭)
Sep 02 Javascript
vue获取input输入值的问题解决办法
Oct 17 Javascript
vuex + axios 做登录验证 并且保存登录状态的实例
Sep 16 Javascript
ES6基础之字符串和函数的拓展详解
Aug 22 Javascript
html中创建并调用vue组件的几种方法汇总
Nov 17 Javascript
js 数据类型判断的方法
Dec 03 #Javascript
用vue设计一个日历表
Dec 03 #Vue.js
JS闭包原理及其使用场景解析
Dec 03 #Javascript
实用的 vue tags 创建缓存导航的过程实现
Dec 03 #Vue.js
Javascript节流函数throttle和防抖函数debounce
Dec 03 #Javascript
如何实现vue的tree组件
Dec 03 #Vue.js
Vue实现图书管理小案例
Dec 03 #Vue.js
You might like
yii2中LinkPager增加总页数和总记录数的实例
2017/08/28 PHP
PHP设计模式之装饰器模式实例详解
2018/02/07 PHP
Yii2.0 RESTful API 基础配置教程详解
2018/12/26 PHP
Javascript实例教程(19) 使用HoTMetal(2)
2006/12/23 Javascript
滚动条变色 隐藏滚动条与双击网页自动滚屏显示代码
2009/12/28 Javascript
如何使用jquery动态加载js,css文件实现代码
2013/04/03 Javascript
禁止选中文字兼容IE、Chrome、FF等
2013/09/04 Javascript
你未必知道的JavaScript和CSS交互的5种方法
2014/04/02 Javascript
JavaScript设计模式之适配器模式介绍
2014/12/28 Javascript
Node.js程序中的本地文件操作用法小结
2016/03/06 Javascript
ajax跨域调用webservice的实现代码
2016/05/09 Javascript
JS使用单链表统计英语单词出现次数
2016/06/16 Javascript
JavaScript trim 实现去除字符串首尾指定字符的简单方法
2016/12/27 Javascript
JS获取鼠标坐标并且根据鼠标位置不同弹出不同内容
2017/06/12 Javascript
微信小程序实现的绘制table表格功能示例
2019/04/26 Javascript
解决vue里a标签值解析变量,跳转页面,前面加默认域名端口的问题
2020/07/22 Javascript
VUE前端从后台请求过来的数据进行转换数据结构操作
2020/11/11 Javascript
[02:56]DOTA2英雄基础教程 巨魔战将
2013/12/10 DOTA
[32:39]完美世界DOTA2联赛循环赛 Forest vs Inki BO2第一场 11.04
2020/11/04 DOTA
python3 读写文件换行符的方法
2018/04/09 Python
Python设计模式之组合模式原理与用法实例分析
2019/01/11 Python
Python结合Window计划任务监测邮件的示例代码
2020/08/05 Python
13个Pandas实用技巧,助你提高开发效率
2020/08/19 Python
python基于openpyxl生成excel文件
2020/12/23 Python
Kate Spade美国官网:纽约新兴时尚品牌,以包包闻名于世
2017/11/09 全球购物
科颜氏香港官方网店:Kiehl’s香港
2021/03/07 全球购物
线程问题:wait()方法是定义在哪个类里面
2015/07/07 面试题
国际金融专业大学生职业生涯规划书
2013/12/28 职场文书
大学应届生的自我评价
2014/03/06 职场文书
3的组成教学反思
2014/04/30 职场文书
同事欢送会致辞
2015/07/31 职场文书
高二英语教学反思
2016/03/03 职场文书
python编写五子棋游戏
2021/05/25 Python
HTML5+CSS+JavaScript实现捉虫小游戏设计和实现
2021/10/16 HTML / CSS
AngularJS实现多级下拉框
2022/03/25 Javascript
Java使用HttpClient实现文件下载
2022/08/14 Java/Android