ECMAScript性能优化技巧与陷阱
大家好,我是程序员小羊!
前言
ECMAScript,即JavaScript,是一种广泛应用于Web开发中的脚本语言。随着现代Web应用的复杂度日益增加,如何优化JavaScript的性能变得至关重要。性能优化不仅能提高应用的响应速度,还能降低资源消耗,改善用户体验。然而,在优化过程中,开发者常常会遇到一些陷阱,导致性能不升反降。本文将详细探讨ECMAScript性能优化的技巧与常见陷阱,帮助开发者更好地编写高效的JavaScript代码。
一、性能优化的核心原则
在讨论具体的优化技巧之前,了解一些基本的性能优化原则是非常重要的:
- 减少不必要的计算:避免重复计算、无用计算,尽量将计算放在必要时刻进行。
- 降低内存消耗:通过合理管理对象的生命周期、避免内存泄漏,减少不必要的内存占用。
- 避免阻塞主线程:JavaScript在浏览器中是单线程执行的,任何耗时操作都会阻塞UI的渲染和用户交互。
- 利用现代特性与工具:使用现代ECMAScript特性和工具,可以让代码更简洁、性能更优。
二、ECMAScript性能优化技巧
2.1 避免全局查找与变量提升
在JavaScript中,访问全局变量比访问局部变量更耗时,因为JavaScript引擎需要沿着作用域链查找变量。为了提高性能,应该尽量减少全局变量的使用,并将频繁使用的全局变量缓存为局部变量。
// 缓存全局变量
const document = window.document;
const elem = document.getElementById('myElement');
此外,尽量避免使用变量提升(Hoisting)带来的性能损失。虽然JavaScript允许在声明之前使用变量,但这样会影响代码的可读性和执行效率。始终在使用变量之前声明变量,避免变量提升引发的问题。
// 错误示范
console.log(a); // undefined
var a = 10;// 优化示范
let a = 10;
console.log(a); // 10
2.2 使用事件委托
事件委托是处理大量DOM元素事件时的常见优化技巧。与其为每个元素都绑定事件,不如将事件绑定在其父元素上,通过事件冒泡机制统一处理子元素的事件。这不仅减少了内存消耗,还提高了性能。
// 示例:使用事件委托处理列表项点击事件
document.getElementById('list').addEventListener('click', function(event) {if (event.target && event.target.nodeName === 'LI') {console.log('List item clicked:', event.target.textContent);}
});
2.3 避免频繁的DOM操作
DOM操作是JavaScript中最耗时的操作之一。频繁的DOM操作会导致页面重排(reflow)和重绘(repaint),从而影响性能。可以通过以下方式优化DOM操作:
- 批量更新DOM:通过一次性插入或修改多个节点,减少DOM的重排和重绘次数。
- 使用文档片段(Document Fragment):在内存中创建文档片段进行DOM操作,最后一次性插入DOM树。
// 错误示范:频繁更新DOM
for (let i = 0; i < 1000; i++) {let item = document.createElement('li');item.textContent = `Item ${i}`;document.getElementById('list').appendChild(item);
}// 优化示范:使用文档片段
let fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {let item = document.createElement('li');item.textContent = `Item ${i}`;fragment.appendChild(item);
}
document.getElementById('list').appendChild(fragment);
2.4 合理使用setTimeout
与setInterval
在Web开发中,setTimeout
和setInterval
是常用的定时器函数。滥用这些函数会导致性能问题,尤其是在间隔时间设置得过短的情况下。应尽量避免使用过短的定时器间隔,并考虑使用requestAnimationFrame
来处理与动画相关的任务。
// 错误示范:过短的setInterval
setInterval(() => {console.log('This can cause performance issues');
}, 1); // 1ms间隔,极可能导致主线程阻塞// 优化示范:使用requestAnimationFrame
function animate() {// 动画逻辑console.log('Animating...');requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
2.5 谨慎使用闭包
闭包在JavaScript中是一个强大的工具,但使用不当会导致内存泄漏,尤其是在长时间运行的Web应用中。闭包会使得函数内部的变量不会被垃圾回收机制释放,除非手动清理。因此,在使用闭包时,确保不会无意中持有不必要的变量。
// 错误示范:未清理的闭包
function createClosure() {let largeArray = new Array(1000000).fill('data');return function() {console.log(largeArray[0]);};
}
let closure = createClosure();
// largeArray始终存在于内存中// 优化示范:手动清理
function createClosure() {let largeArray = new Array(1000000).fill('data');return function() {console.log(largeArray[0]);largeArray = null; // 手动清理};
}
let closure = createClosure();
2.6 减少内存泄漏
内存泄漏是影响JavaScript性能的重要问题,尤其是在单页应用(SPA)中。以下是常见的内存泄漏原因及其解决方案:
- 未解除的事件监听器:确保在不再需要时移除事件监听器。
- DOM节点的循环引用:避免DOM节点与JavaScript对象之间的循环引用,必要时手动断开引用。
- 定时器未清理:在组件卸载或页面离开时清理不再需要的定时器。
// 错误示范:未清理的事件监听器
function attachListener() {window.addEventListener('resize', function() {console.log('Window resized');});
}
// 在不再需要时,事件监听器仍然存在// 优化示范:清理事件监听器
function attachListener() {function onResize() {console.log('Window resized');}window.addEventListener('resize', onResize);return function() {window.removeEventListener('resize', onResize);};
}
let detach = attachListener();
// 在不再需要时调用detach以清理监听器
2.7 使用高效的循环结构
在JavaScript中,循环结构对性能有显著影响。优化循环的执行效率可以提高整体代码性能:
- 使用
for
循环代替forEach
:传统的for
循环在性能上通常优于forEach
和其他高级循环方法,尤其是在需要遍历大数组时。 - 减少循环中的计算:将循环中的不变计算移出循环体,避免不必要的重复计算。
// 错误示范:使用forEach和循环内计算
const data = [1, 2, 3, 4, 5];
data.forEach((item, index) => {console.log(item * index * Math.random());
});// 优化示范:使用for循环并移出不变计算
const data = [1, 2, 3, 4, 5];
const random = Math.random();
for (let i = 0; i < data.length; i++) {console.log(data[i] * i * random);
}
2.8 使用惰性加载与代码拆分
现代Web应用通常需要加载大量的JavaScript代码,这会影响页面的加载速度和初次渲染性能。通过惰性加载(Lazy Loading)和代码拆分(Code Splitting),可以减少初始加载时间,提高应用的性能。
- 惰性加载:只在需要时加载特定的代码或模块,避免初次加载过多的资源。
- 代码拆分:使用Webpack等工具将代码分割成多个块(chunks),按需加载,减少初始加载体积。
// 示例:动态导入模块(惰性加载)
function loadComponent() {import('./MyComponent.js').then(module => {const MyComponent = module.default;// 使用MyComponent});
}
三、常见的性能陷阱
3.1 未优化的递归
递归是解决某些问题的有效方法,但在JavaScript中递归使用不当会导致性能问题,甚至导致栈溢出。应尽量使用尾递归优化(如果引擎支持)或转化为迭代。
// 错误示范:未优化的递归
function factorial(n) {if (n <= 1) return 1;return n * factorial(n - 1);
}// 优化示范:使用尾递归(如支持)或迭代
function factorial(n, acc = 1) {if (n <= 1) return acc;return factorial(n - 1, n * acc);
}
// 或者使用迭代
function factorialIterative(n) {let result = 1;for (let i = 2; i <= n; i++) {result *= i;}return result;
}
3.2 误用eval
与with
eval
和with
是JavaScript中的两大性能陷阱。eval
允许执行动态代码,但它会强制JavaScript引擎取消一些优化,导致性能下降。with
则会引入新的作用域,增加变量解析的复杂性,同样影响性能。应尽量避免使用这两个语法结构。
// 错误示范:使用eval
const code = 'console.log("Hello World")';
eval(code); // 不仅影响性能,还存在安全风险// 优化示范:避免使用eval
const code = () => console.log("Hello World");
code();
3.3 无效的去抖与节流
去抖(Debounce)和节流(Throttle)是优化频繁事件触发(如滚动、输入)的常用技术。但不正确的实现或配置会导致优化无效,甚至引发性能问题。确保在实现去抖或节流时合理设定时间间隔,并根据实际需求调整策略。
// 错误示范:不合理的去抖实现
function debounce(fn, delay) {let timer;return function() {clearTimeout(timer);timer = setTimeout(fn, delay);};
}
window.addEventListener('resize', debounce(() => {console.log('Resized');
}, 1000)); // 间隔过长,影响体验// 优化示范:合理的去抖实现
function debounce(fn, delay) {let timer;return function() {clearTimeout(timer);timer = setTimeout(fn, delay);};
}
window.addEventListener('resize', debounce(() => {console.log('Resized');
}, 100)); // 合理的时间间隔
结尾
ECMAScript的性能优化需要开发者在代码编写、结构设计和工具使用等各方面做出合理选择。通过减少不必要的计算和内存消耗、避免阻塞主线程、利用现代ECMAScript特性以及合理使用工具和框架,可以大幅提升JavaScript应用的性能。同时,开发者还应当小心避免一些常见的性能陷阱,如滥用
eval
、不当的递归、以及无效的去抖和节流。在不断实践和优化的过程中,开发者可以逐步掌握提升JavaScript性能的关键技巧,打造出高效、流畅的Web应用。
今天这篇文章就到这里了,大厦之成,非一木之材也;大海之阔,非一流之归也。感谢大家观看本文
相关文章:

ECMAScript性能优化技巧与陷阱
大家好,我是程序员小羊! 前言 ECMAScript,即JavaScript,是一种广泛应用于Web开发中的脚本语言。随着现代Web应用的复杂度日益增加,如何优化JavaScript的性能变得至关重要。性能优化不仅能提高应用的响应速度&#x…...

c++实现B树(上)
哈喽啊!好久不见,甚是想念!失踪人口要回归了,时隔一个多月小吉我终于要更新blog了🎉。在停更的一个多月中,小吉也有在好好学习提升自己,立志给大家呈现好文章。 现在让我们进入正题吧…...

【机器学习】深度强化学习–RL的基本概念、经典场景以及算法分类
引言 深度强化学习(Deep Reinforcement Learning, DRL)是机器学习的一个分支,它结合了深度学习(Deep Learning)和强化学习(Reinforcement Learning, RL)的技术 文章目录 引言一、深度强化学习–…...

【git】将本地文件上传到github
安装git 选择一个文件夹作为git仓库,cd到文件夹输入 git init文件夹出现.git文件夹,该文件夹默认为隐藏文件夹,设置为不隐藏 在cmd中输入 ssh-keygen -t rsa -C "xxxxxx.com"该邮箱为github邮箱,然后一路enter出现以…...

安卓应用开发学习:手机摇一摇功能应用尝试--摇骰子和摇红包
一、引言 前几天,我发布的日志《安卓应用开发学习:查看手机传感器信息》记录了如何查看手机传感器的信息,通过上述的方法,可以看到我的OPPO手机支持19种传感器。本篇日志就记录一下常见的加速度传感器的典型应用——“摇一摇”功…...

HTML中的<fieldset>标签元素框的使用
HTML 提供的 <fieldset> 标签用于在表单中分组相关元素。 <fieldset> 标签会在相关元素周围绘制一个框。 <legend> 标签为 fieldset 元素定义标题。 语法如下: <fieldset><legend>标题</legend><!-- 元素内容... -->…...

Linux驱动入门实验班——SR501红外模块驱动(附百问网视频链接)
目录 一、工作方式 二、接口图 三、编写思路 1.构造file_operations结构体 2.实现read函数 3.编写入口函数 4.编写中断处理函数 5.编写出口函数 6.声明出入口函数以及协议 四、源码 五、课程链接 一、工作方式 SR501人体红外感应模块有两种工作模式: …...

windows C++- Com技术简介(上)
在介绍C和winrt与COM组件技术的关系之前,有必要介绍一下com组件技术,这项技术比较古老,但是它一直作为windows的基石存在。COM 是一类独立于平台且面向对象的分布式系统,用于创建可交互的二进制软件组件。 COM 技术是 Microsoft O…...

Jenkins持续集成工具学习
一、从装修厨房看项目开发效率优化 二、持续集成工具 三、JavaEE项目部署方式对比 四、JenkinsSVN持续集成环境搭建 五、JenkinsGitHub持续集成环境搭建...

Redis:查询是否包含某个字符/字符串之三
上一篇:Redis:查询是否包含某个字符/字符串之二-CSDN博客 摘要: 遍历key,在跟进value的类型遍历value是否包含指定字符串 search_strings ,这里使用redis-py库,默认只能处理utf-8编码,如果存在…...

【Redis】数据类型详解及其应用场景
目录 Redis 常⻅数据类型预备知识基本全局命令小结 数据结构和内部编码单线程架构引出单线程模型为什么单线程还能这么快 Redis 常⻅数据类型 Redis 提供了 5 种数据结构,理解每种数据结构的特点对于 Redis 开发运维⾮常重要,同时掌握每种数据结构的常⻅…...

PARA-Drive:设计并行模型实现端到端自动驾驶
论文链接 https://openaccess.thecvf.com/content/CVPR2024/papers/Weng_PARA-Drive_Parallelized_Architecture_for_Real-time_Autonomous_Driving_CVPR_2024_paper.pdfhttps://openaccess.thecvf.com/content/CVPR2024/papers/Weng_PARA-Drive_Parallelized_Architecture_fo…...

vs2022 x64 C/C++和汇编混编 遇到的坑
vs2022 x64 C/C和汇编混编 遇到的坑 遇到的问题二、问题复现1.出错代码2.问题分析2.1 堆栈对齐问题 3.解决方案 总结奇数和偶数个寄存器的影响为什么 sub rsp, 8 对奇数个寄存器有用?结论 遇到的问题 0x00007FFFFAE24A29 (msvcp140.dll)处(位于 TestCompileConsole…...

PHP概述、环境搭建与基本语法讲解
目录 【学习目标、重难点知识】 什么是网站? 1. PHP 介绍 1.1. PHP 概述 1.1.1. PHP 是什么? 1.1.2. PHP 都能做什么? 1.2. PHP 环境搭建 1.2.1. PhpStudy 2. PHP 基本语法 2.1. PHP 语法入门 2.1.1. 第一个 PHP 程序 2.1.2. PHP …...

实现信创Linux麦克风摄像头录制(源码,银河麒麟、统信UOS)
随着信创国产化浪潮的来临,在国产操作系统上的应用开发的需求越来越多,其中一个就是需要在银河麒麟或统信UOS上实现录制摄像头视频和麦克风声音,将它们录制成一个mp4文件。那么这个要如何实现了? 一. 技术方案 要完成这些功能&a…...

深度学习9--目标检测
1.概念介绍 目标检测不仅可以检测数字,而且可以检测动物的种类、汽车的种类等。例如,自动驾驶车辆需要自动识别前方物体是车辆还是行人,需要自动识别道路两 旁的指示牌和前方的红绿灯颜色。对于自动检测的算法,有两个要求…...

第131天:内网安全-横向移动Kerberos 攻击SPN扫描WinRMWinRSRDP
案例一:域横向移动-RDP-明文&NTLM RDP利用的三种方式 1.直接在当前被控主机上进行远程连接 2.建立节点进行连接 3.端口转发,(访问当前主机的2222端口等于访问目标的3389) 第一种方式(动静太大) 直接利用被控主机进行远程连接…...

微信小程序的四种弹窗使用
在做小程序的过程中,弹窗也算是非常实用的功能了,这几天写的几个功能就用到了弹窗,也可能是初学者的问题,比较菜,想找一个可以带图片的自定义的弹窗,,这里简单介绍一下官方封装好的四个弹窗…...

我的第一个CUDA程序
MatAdd算法 实现两个矩阵对应元素相加 #include <stdio.h> #include <stdlib.h>// 矩阵加法函数 void MatAdd(int height, int width) {// 在主机内存中为 A、B 和 C 分配内存float* A (float*)malloc(height * width * sizeof(float));float* B (float*)malloc…...

workerman下的webman路由浏览器跨域的一种问题
软件版本 "php": ">7.2", "workerman/webman-framework": "^1.5.0",问题情景 使用“分组路由”做API接口前后端分离跨域,在接口测试工具调试是能正常获取数据的;但在网页浏览器上调试就遇到了CORS、404的错…...

Windows11 -MASKRCNN-部署测试
文章目录 Detectron2环境配置搭建python 环境安装Cuda \CUDNN 、PyTorch、 torchvision、cudatoolkit1、Cuda \CUDNN2、 PyTorch、 torchvision、cudatoolkit进入python测试:错误信息 3、detectron2环境在安装detecteron中,遇到报错:编译的时…...

函数(子程序)的常见、易混淆概念详解【对初学者有帮助】
C语⾔中的函数也被称做子程序,意思就是⼀个完成某项特定的任务的⼀小段代码。 C语⾔标准中提供了许多库函数,点击下面的链接可以查看c语言的库函数和头文件。 C/C官⽅的链接:https://zh.cppreference.com/w/c/header 目录 一、函数头与函…...

TiDB-从0到1-DM工具
TiDB从0到1系列 TiDB-从0到1-体系结构TiDB-从0到1-分布式存储TiDB-从0到1-分布式事务TiDB-从0到1-MVCCTiDB-从0到1-部署篇TiDB-从0到1-配置篇TiDB-从0到1-集群扩缩容TiDB-从0到1-数据导出导入TiDB-从0到1-BR工具 一、DM原理 支持全量抽取数据\检测新的数据变化同步到下游实例…...

AppScan——Web 应用安全扫描的得力工具
一、引言 在当今数字化时代,Web 应用成为企业业务的重要支撑,但同时也面临着各种安全威胁。AppScan 作为一款专业的 Web 应用安全扫描工具,为保障 Web 应用的安全性提供了有力的支持。本文将对 AppScan 进行详细介绍,包括其功能、…...

虚幻5|AI行为树,进阶篇
一,打开敌人的角色蓝图,编写以下蓝图,该蓝图只是创建一个敌人并非ai行为树 1.编写蓝图 2.打开主界面,创建一个导航网格体积,上一章都有讲,在添加体积这里面,找到导航网格体积,点击创…...

在 Spring Boot 中配置 Tomcat 监听多个端口
在现代微服务架构中,应用程序可能需要监听多个端口,以支持不同的服务或协议。Spring Boot 提供了灵活的配置选项,使得这一需求变得简单而高效。本文将介绍如何在 Spring Boot 中配置 Tomcat 以监听多个端口,并简要说明其中一些关键…...

stm32f407新建项目工程及烧录
1、新建一个文件夹,打开keil5将项目工程放入文件夹中 2、弹出选择对应型号设备 3、弹出选择对应库 可以看见出现下图:感叹号表示有错 最后如图所示:点击ok就行了 4、创建对应的文件夹存放文件 4、建立main.c 5、添加对应的设置 最后写一个空白…...

c++中加不加const的值传递和引用传递的区别
文章目录 可以修改参数值的比较值传递(int x)和引用传递(int &x)使用const不修改参数值的比较值传递(const int x)和引用传递(const int &x)1. const int x 示例2. const int &x 示例 可以修改参数值的比较值传递(int x)和引用传递(int &x) #include <iost…...

Qt的窗口设置
本文介绍Qt的窗口设置。 采用Qt开发界面程序,会涉及到窗口的设置,如窗口标题栏是否显示,是否有最小,最大化按钮等,窗口当前显示最小化,最大化等。本文简要介绍常用的窗口设置方法。 1.窗口属性 窗口属性…...

51单片机-LCD1602显示屏
简介 是一个液晶显示屏,通过电压对显示区域进行控制,有电就显示。 能够同时显示32个字符,分为两行,一行显示16个字符。可以显示的内容只能是字母、数字或者一些特殊符号。 使用ASCII码来让LCD1602来显示对应的字符。 电路图 …...