从实际开发中来看JavaScript事件循环的使用场景

简介: 本文是介绍结合DOM事件流和JavaScript事件循环解决一个工作中的实际问题的过程,很多东西不只是面试的时候才会用得到

背景


近期在开发某需求的时候,遇到了一个需要同时有拖拽和点击的场景,首席CV(复制粘贴)工程师如我🐶,当然是使用react-draggable开源组件了,大概的代码结构(已脱敏)如下

import Draggable from 'react-draggable';
import React from 'react';
const Demo = (props) => {
  return (
    <Draggable>
      <div onClick={() => { console.log('被点击了!') }}>
        拖拽区域
      </div>
    </Draggable>
  )
}  
复制代码

但是,这样存在一个问题在拖拽完毕之后,拖拽区会响应一次点击事件,这是不合理也是不符合预期的。

梳理了一react-draggable原理和业务代码的流程,整个过程可以总结为下图👇

image.png

解法


标志位


我的方法是加一个canClick标志位,用来标识当前是否可以点击,在拖动时设置它为false,拖动结束时设置它为true

import Draggable from 'react-draggable';
import React from 'react';
const Demo = (props) => {
  let canClick = true;
  const _handleDraging = (e) => {
      canClick = false;
  }
  const _handleDragStop = (e) => {
      canClick = true;
  }
  return (
    <Draggable onDrag={_handleDraging} onStop={_handleDragStop}>
      <div onClick={() => { if(canClick) { console.log('被点击了!') } }}>
        拖拽区域
      </div>
    </Draggable>
  )
}  
复制代码

但是这样子依然不行,仔细思考之后发现这里会涉及到一些事件循环的原理,JavaScript是单线程语言,要实现非阻塞的去处理一些异步任务,就会把相关的异步回调放入到事件队列中,例如DOM事件回调这样的方法会被放入到宏事件(macroTask)队列中去,所以当前宏事件(macroTask)队列应该是这样的:


image.png

如上图,宏事件(macroTask)队列从左往右执行,在_handleDragStop方法中,已经把标志位canClick置为了true,所以在拖拽结束时,仍然是可以响应click事件的。

结合事件循环宏事件(macroTask)队列的执行,我们可以对代码做一些小修改,即可以达到目的:

import Draggable from 'react-draggable';
import React from 'react';
const Demo = (props) => {
  let canClick = true;
  const _handleDraging = (e) => {
      canClick = false;
  }
  const _handleDragStop = (e) => {
    setTimeout(() => {
      canClick = true;
    }, 0);
  }
  return (
    <Draggable onDrag={_handleDraging} onStop={_handleDragStop}>
      <div onClick={() => { if(canClick) { console.log('被点击了!') } }}>
        拖拽区域
      </div>
    </Draggable>
  )
}  
复制代码

此时,宏事件(macroTask)队列就变成了下图这样子👇


image.png

把设置canClick=true的语句放到setTimeout中,这样就把执行顺序放到了onClick事件回调的后面,标志位就开始生效了!


事件循环(eventLoop)


什么是事件循环?貌似这个概念只有在面试的时候会被问到,然后在面试完一周之内就会忘记,别笑,因为我之前面试也问过别人这个概念😅

事实上,理解事件循环的工作方式对于代码优化很重要,有时对于正确的架构也很重要。


概念


简单的说,事件循环(eventLoop)是单线程的JavaScript在处理异步事件时进行的一种循环过程,具体来讲,对于异步事件它会先加入到事件队列中挂起,等主线程空闲时会去执行事件队列中的事件。


宏事件(macroTask)队列和微事件(microTask)队列


因为异步事件种类的不同、执行优先级不同,不同的异步事件被分为宏任务和微任务。

常见的宏事件:

  • setTimeout
  • setInterval
  • UI 事件
  • 网络请求
  • ....

常见的微事件

  • Promise.resolve、Promise.reject
  • Mutation Observer
  • queueMicrotask(func)

宏任务和微任务的执行顺序是这样的:当前执行栈执行完毕时会立刻先处理所有微任务队列中的事件,然后再去宏任务队列中取出一个事件。同一次事件循环中,微任务永远在宏任务之前执行


事件循环的其他应用


拆分CPU过载任务


demo:intensiveCalculate.html

对于一些CPU耗时过长的任务,我们可以使用事件循环来拆分避免浏览器长时间不响应页面事件(比如点击操作)

假想有一个很长的计算任务,耗时超过5秒以上,那么在这5秒以内,界面会被阻塞住,用户在界面上的所有的操作都无法响应,如果我们并不是要计算完成才可以响应页面(比如一个超长的文档做高亮),那么我们可以使用宏事件拆分计算任务来做优化。

例如这样子一个超长的计算任务,大概耗时8秒以上

let i = 0;
let start = Date.now();
function count() {
    // 做一个繁重的任务
    for (let j = 0; j < 1e9; j++) {
        i++;
    }
    alert("Done in " + (Date.now() - start) + 'ms');
}
const btn = document.getElementById('start');
btn.onclick = count;
复制代码

如果这些计算并非是响应页面前必须的,那么可以这样子优化:

let i = 0;
let start = Date.now();
function count() {
    setTimeout(() => {
        count();
    }, 0);
    // 做一个繁重的任务
    for (let j = 0; j < 1e6; j++) {
        i++;
    }
    if (i === 1e9) {
        alert("Done in " + (Date.now() - start) + 'ms');
    }
}
const btn = document.getElementById('start');
btn.onclick = count;
复制代码

代码把一个连续的计算任务分切成了1000个小任务,并且不会阻塞主线程的工作,完美解决。


相关文章
|
29天前
|
Web App开发 JavaScript 前端开发
Node.js 是一种基于 Chrome V8 引擎的后端开发技术,以其高效、灵活著称。本文将介绍 Node.js 的基础概念
Node.js 是一种基于 Chrome V8 引擎的后端开发技术,以其高效、灵活著称。本文将介绍 Node.js 的基础概念,包括事件驱动、单线程模型和模块系统;探讨其安装配置、核心模块使用、实战应用如搭建 Web 服务器、文件操作及实时通信;分析项目结构与开发流程,讨论其优势与挑战,并通过案例展示 Node.js 在实际项目中的应用,旨在帮助开发者更好地掌握这一强大工具。
45 1
|
20天前
|
Web App开发 JavaScript 前端开发
Node.js开发
Node.js开发
34 13
|
27天前
|
存储 JavaScript 前端开发
深入浅出Node.js后端开发
在数字化时代的浪潮中,后端开发作为连接用户与数据的桥梁,扮演着至关重要的角色。本文将以Node.js为例,深入探讨其背后的哲学思想、核心特性以及在实际项目中的应用,旨在为读者揭示Node.js如何优雅地处理高并发请求,并通过实践案例加深理解。无论你是初学者还是有一定经验的开发者,这篇文章都将为你提供新的视角和思考。
|
24天前
|
Web App开发 开发框架 JavaScript
深入浅出Node.js后端开发
本文将带你领略Node.js的魅力,从基础概念到实践应用,一步步深入理解并掌握Node.js在后端开发中的运用。我们将通过实例学习如何搭建一个基本的Web服务,探讨Node.js的事件驱动和非阻塞I/O模型,以及如何利用其强大的生态系统进行高效的后端开发。无论你是前端开发者还是后端新手,这篇文章都会为你打开一扇通往全栈开发的大门。
|
27天前
|
Web App开发 开发框架 JavaScript
深入浅出Node.js后端开发
在这篇文章中,我们将一起探索Node.js的奇妙世界。无论你是刚接触后端开发的新手,还是希望深化理解的老手,这篇文章都适合你。我们将从基础概念开始,逐步深入到实际应用,最后通过一个代码示例来巩固所学知识。让我们一起开启这段旅程吧!
|
23天前
|
Web App开发 JavaScript 前端开发
深入浅出Node.js后端开发
本文将带领读者从零基础开始,一步步深入到Node.js后端开发的精髓。我们将通过通俗易懂的语言和实际代码示例,探索Node.js的强大功能及其在现代Web开发中的应用。无论你是初学者还是有一定经验的开发者,这篇文章都将为你提供新的见解和技巧,让你的后端开发技能更上一层楼。
|
26天前
|
JavaScript 前端开发 API
深入理解Node.js事件循环及其在后端开发中的应用
本文旨在揭示Node.js的核心特性之一——事件循环,并探讨其对后端开发实践的深远影响。通过剖析事件循环的工作原理和关键组件,我们不仅能够更好地理解Node.js的非阻塞I/O模型,还能学会如何优化我们的后端应用以提高性能和响应能力。文章将结合实例分析事件循环在处理大量并发请求时的优势,以及如何避免常见的编程陷阱,从而为读者提供从理论到实践的全面指导。
|
27天前
|
Web App开发 JavaScript 前端开发
深入浅出Node.js后端开发
本文将带你走进Node.js的世界,从基础到进阶,逐步解析Node.js在后端开发中的应用。我们将通过实例来理解Node.js的异步特性、事件驱动模型以及如何利用它处理高并发请求。此外,文章还会介绍如何搭建一个基本的Node.js服务器,并探讨如何利用现代前端框架与Node.js进行交互,实现全栈式开发。无论你是初学者还是有一定经验的开发者,这篇文章都将为你提供新的视角和深入的理解。
22 4
|
28天前
|
前端开发 JavaScript 关系型数据库
基于 Vue2.0 + Nest.js 全栈开发的后台应用
Vue2 Admin 是一个基于 Vue2 和 Ant Design Pro 开发的前端项目,配合 Nest.js 构建的后端,提供了一个完整的全栈后台应用解决方案。该项目支持动态国际化、用户权限管理、操作日志记录等功能,适合全栈开发者学习参考。线上预览地址:https://vue2.baiwumm.com/,用户名:Admin,密码:abc123456。
|
29天前
|
开发框架 JavaScript 前端开发
TypeScript 是一种静态类型的编程语言,它扩展了 JavaScript,为 Web 开发带来了强大的类型系统、组件化开发支持、与主流框架的无缝集成、大型项目管理能力和提升开发体验等多方面优势
TypeScript 是一种静态类型的编程语言,它扩展了 JavaScript,为 Web 开发带来了强大的类型系统、组件化开发支持、与主流框架的无缝集成、大型项目管理能力和提升开发体验等多方面优势。通过明确的类型定义,TypeScript 能够在编码阶段发现潜在错误,提高代码质量;支持组件的清晰定义与复用,增强代码的可维护性;与 React、Vue 等框架结合,提供更佳的开发体验;适用于大型项目,优化代码结构和性能。随着 Web 技术的发展,TypeScript 的应用前景广阔,将继续引领 Web 开发的新趋势。
37 2