在实例中重学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动态创建表格,删除行列的小例子
Jul 20 Javascript
JS冒泡事件的快速解决方法
Dec 16 Javascript
使用jQuery实现验证上传图片的格式与大小
Dec 03 Javascript
BootStrap中Datepicker控件带中文的js文件
Aug 10 Javascript
JQuery和html+css实现带小圆点和左右按钮的轮播图实例
Jul 22 jQuery
js下拉菜单生成器dropMenu使用方法详解
Aug 01 Javascript
bootstrap-table.js扩展分页工具栏(增加跳转到xx页)功能
Dec 28 Javascript
微信二次分享报错invalid signature问题及解决方法
Apr 01 Javascript
记一次用vue做的活动页的方法步骤
Apr 11 Javascript
微信小程序实现传递多个参数与事件处理
Aug 12 Javascript
vue 使用element-ui中的Notification自定义按钮并实现关闭功能及如何处理多个通知
Aug 17 Javascript
JavaScript 斐波那契数列 倒序输出 输出100以内的质数代码实例
Sep 11 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
PHP获取当前页面完整URL的实现代码
2013/06/10 PHP
ThinkPHP 404页面的设置方法
2015/01/14 PHP
JavaScript For Beginners(转载)
2007/01/05 Javascript
javascript之可拖动的iframe效果代码
2008/08/01 Javascript
JavaScript null和undefined区别分析
2009/10/14 Javascript
jquery 定位input元素的几种方法小结
2013/07/28 Javascript
jquery统计用户选中的复选框的个数
2014/06/06 Javascript
轻松学习jQuery插件EasyUI EasyUI实现拖放商品放置购物车
2015/11/30 Javascript
JavaScript弹出对话框的三种方式
2016/03/23 Javascript
JQuery的attr 与 val区别
2016/06/12 Javascript
Node.js学习之查询字符串解析querystring详解
2017/09/28 Javascript
通过 JS 判断页面是否有滚动条的实现方法
2018/04/05 Javascript
JavaScript数组特性与实践应用深入详解
2018/12/30 Javascript
nodejs log4js 使用详解
2019/05/31 NodeJs
JS数组降维的实现Array.prototype.concat.apply([], arr)
2020/04/28 Javascript
在VUE style中使用data中的变量的方法
2020/06/19 Javascript
Javascript confirm多种使用方法解析
2020/09/25 Javascript
[02:57]DOTA2亚洲邀请赛小组赛第四日 赛事回顾
2015/02/02 DOTA
零基础写python爬虫之爬虫框架Scrapy安装配置
2014/11/06 Python
使用Python写个小监控
2016/01/27 Python
Python中的with语句与上下文管理器学习总结
2016/06/28 Python
举例讲解Python编程中对线程锁的使用
2016/07/12 Python
python 日期操作类代码
2018/05/05 Python
详解Python 协程的详细用法使用和例子
2018/06/15 Python
基于python的Paxos算法实现
2019/07/03 Python
安装docker-compose的两种最简方法
2019/07/30 Python
Python实现直方图均衡基本原理解析
2019/08/08 Python
Python CSV文件模块的使用案例分析
2019/12/21 Python
TensorFlow梯度求解tf.gradients实例
2020/02/04 Python
护士岗前培训自我评鉴
2014/02/28 职场文书
公司建议书怎么写
2014/05/15 职场文书
企业领导对照检查材料
2014/08/20 职场文书
学校周年庆活动方案
2014/08/22 职场文书
组织生活会表态发言材料
2014/10/17 职场文书
员工2014年度工作总结
2014/12/09 职场文书
mysql中关键词exists的用法实例详解
2022/06/10 MySQL