在实例中重学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 相关文章推荐
JavaScript处理解析JSON数据过程详解
Sep 11 Javascript
Javascript OOP之面向对象
Jul 31 Javascript
jsp 网站引入外部css或者js失效问题解决
Oct 31 Javascript
无循环 JavaScript(map、reduce、filter和find)
Apr 08 Javascript
React学习笔记之事件处理(二)
Jul 02 Javascript
微信小程序实现给嵌套template模板传递数据的方式总结
Dec 18 Javascript
Vue开发实现吸顶效果的示例代码
Aug 21 Javascript
Jquery获取radio选中值实例总结
Jan 17 jQuery
Angular7创建项目、组件、服务以及服务的使用
Feb 19 Javascript
javascript实现自由编辑图片代码详解
Jun 21 Javascript
基于vue 实现表单中password输入的显示与隐藏功能
Jul 19 Javascript
详解Vue template 如何支持多个根结点
Feb 10 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对文件进行hash运算的方法
2015/04/03 PHP
IE中radio 或checkbox的checked属性初始状态下不能选中显示问题
2009/07/25 Javascript
JavaScript初学者建议:不要去管浏览器兼容
2014/02/04 Javascript
常用的jquery模板插件——jQuery Boilerplate介绍
2014/09/23 Javascript
JavaScript中property和attribute的区别详细介绍
2015/03/03 Javascript
jQuery的animate函数实现图文切换动画效果
2015/05/03 Javascript
nodejs创建web服务器之hello world程序
2015/08/20 NodeJs
jquery实现点击弹出带标题栏的弹出层(从右上角飞入)效果
2015/09/19 Javascript
Javascript 高性能之递归,迭代,查表法详解及实例
2017/01/08 Javascript
canvas实现流星雨的背景效果
2017/01/13 Javascript
利用node.js如何搭建一个简易的即时响应服务器
2017/05/28 Javascript
EasyUI创建人员树的实例代码
2017/09/15 Javascript
angularjs实现猜大小功能
2017/10/23 Javascript
Vim快速合并行及vim 将文件所有行合并到一行
2017/11/27 Javascript
Angular实现较为复杂的表格过滤,删除功能示例
2017/12/23 Javascript
JavaScript使用math.js进行精确计算操作示例
2018/06/19 Javascript
微信小程序实现横向增长表格的方法
2018/07/24 Javascript
vue单页应用的内存泄露定位和修复问题小结
2019/08/02 Javascript
Vue实例的对象参数options的几个常用选项详解
2019/11/08 Javascript
[46:27]DOTA2上海特级锦标赛主赛事日 - 1 胜者组第一轮#2LGD VS MVP.Phx第一局
2016/03/02 DOTA
Python 匹配任意字符(包括换行符)的正则表达式写法
2009/10/29 Python
Python3 中文文件读写方法
2018/01/23 Python
Python 解决中文写入Excel时抛异常的问题
2018/05/03 Python
Python实现的redis分布式锁功能示例
2018/05/29 Python
python+flask编写一个简单的登录接口
2020/11/13 Python
12个不为大家熟知的HTML5设计小技巧
2016/06/02 HTML / CSS
全球速卖通:AliExpress(国际版淘宝)
2017/09/20 全球购物
英国领先的维生素和营养补充剂直接供应商:Healthspan
2019/04/22 全球购物
英国健康和美容技术产品购物网站:CurrentBody
2019/07/17 全球购物
回馈慈善的设计师太阳镜:DIFF eyewear
2019/10/17 全球购物
绿化先进工作者事迹材料
2014/01/30 职场文书
安全负责人任命书
2014/06/06 职场文书
2014最新预备党员思想汇报范文:中国梦,我的梦
2014/10/25 职场文书
公司财务部岗位职责
2015/04/14 职场文书
CSS 制作波浪效果的思路
2021/05/18 HTML / CSS
Java 数组的使用
2022/05/11 Java/Android