Jym好😘,我是月弦笙音,今天给大家分享 如何避免拖慢js执行速度常见的坑点 ,嘎嘎的😍,看下面
软件运行速度、性能及用户体验。这在 咱Web 开发领域占据主导地位,尤其是 Js 和 Node。缓慢或卡顿的网站是业余爱好者的标志(这么说你不会怼我吧😂),而流畅、优化的体验会让用户感到高兴,这就叫专业。
但是,做到真正高性能的 Web 应用程序充满了陷阱。坑点一抓一大把,这些陷阱可能会在你不知不觉中拖累 Js 的步伐。微小的疏忽会使你的代码膨胀并质变地拖慢运行速度,最终可能成为屎山代码!😂
有时候,你警惕地缩小你的代码并利用缓存、打包等等......然而,你的网站有时仍然感觉异常缓慢,甚至卡的一批,浏览器都给整崩了。UI 在滚动或单击按钮时断断续续。页面需要亿万年的时间才能加载,这时候用户就骂骂咧咧的走了,从而导致用户体验差而流失用户,我擦,这就很...😂
事实证明,我们有许多常见的方法会无意中减慢 Js 的速度。随着时间的推移,反模式可能会阻碍网站性能。
今天,我们将重点介绍 19 大性能陷阱,这些陷阱可能会悄悄地减慢 Js 应用程序的速度。我们将通过说明性示例和可操作的解决方案来探索导致它们的原因,以优化你的代码。
识别和消除这些危害是打造令用户满意的流畅 Web 体验的关键。所以,让我们嘎嘎开始吧!😎
1. 不正确的变量声明和范围
我们都深有所感,第一次学习 Js 时,很想全局声明所有变量。但是,这会导致未来的问题。让我们看一个例子:
scss
// globals.js
var color = 'blue';
function printColor() {
console.log(color);
}
printColor(); // Prints 'blue'
这很好用,但想象一下,如果我们加载了另一个脚本:
ini
// script2.js
var color = 'red';
printColor(); // Prints 'red'!
因为是全局的,script2.js 覆盖了它!要解决此问题,请尽可能在函数中本地声明变量:color
scss
function printColor() {
var color = 'blue'; // local variable
console.log(color);
}
printColor(); // Prints 'blue'
现在,其他脚本中的更改不会影响 .printColor
在不必要时在全局范围内声明变量是一种反模式。尝试将全局变量限制为配置常量。对于其他变量,请在尽可能小的范围内本地声明它们。
2. 低效的 DOM 操作
更新 DOM 元素时,批量更改,而不是一次操作一个节点。请看这个例子:
ini
const ul = document.getElementById('list');
for (let i = 0; i < 10; i++) {
const li = document.createElement('li');
li.textContent = i;
ul.appendChild(li);
}
这将逐个追加列表项。最好先构建一个字符串,然后再设置:.innerHTML
ini
const ul = document.getElementById('list');
let html = '';
for (let i = 0; i < 10; i++) {
html += `<li>${i}</li>`;
}
ul.innerHTML = html;
构建字符串可以最大限度地减少回流。我们只更新一次DOM,而不是10次。 对于多个更新,构建更改,然后在最后应用。或者更好的是,使用DocumentFragment来批处理追加内容。
3. 过多的DOM操作
频繁的DOM更新会降低性能。考虑一个将消息插入页面的聊天应用程序
拉胯:
ini
// New message received
const msg = `<div>${messageText}</div>`;
chatLog.insertAdjacentHTML('beforeend', msg);
这简单地插入到每条消息上。最好限制更新
nice:
ini
let chatLogHTML = '';
const throttleTime = 100; // ms
// New message received
chatLogHTML += `<div>${messageText}</div>`;
// Throttle DOM updates
setTimeout(() => {
chatLog.innerHTML = chatLogHTML;
chatLogHTML = '';
}, throttleTime);
现在,我们最多每 100 毫秒更新一次,使 DOM 操作保持在较低水平。
对于高度动态的 UI,请考虑像 React 这样的虚拟 DOM 库。这些使用虚拟表示最大限度地减少了 DOM 操作。
4. 缺乏事件事件委派
将事件侦听器附加到许多元素会产生不必要的开销。考虑一个每行都有删除按钮的表:
拉胯:
ini
const rows = document.querySelectorAll('table tr');
rows.forEach(row => {
const deleteBtn = row.querySelector('.delete');
deleteBtn.addEventListener('click', handleDelete);
});
这将为每个删除按钮添加一个侦听器。最好使用事件委派:
nice:
ini
const table = document.querySelector('table');
table.addEventListener('click', e => {
if (e.target.classList.contains('delete')) {
handleDelete(e);
}
});
现在,.更少的内存开销。<table>
事件委派利用事件冒泡。一个侦听器可以处理来自多个后代的事件。如果适用,请使用委派。
5. 低效的字符串连接
在循环中连接字符串时,性能会受到影响。请考虑以下代码:
css
let html = '';
for (let i = 0; i < 10; i++) {
html += '<div>' + i + '</div>';
}
创建新字符串需要内存分配。最好使用数组:
ini
const parts = [];
for (let i = 0; i < 10; i++) {
parts.push('<div>', i, '</div>');
}
const html = parts.join('');
构建数组可以最大程度地减少中间字符串。 在末尾连接一次。.join()
对于多个字符串添加,请使用数组联接。此外,请考虑嵌入值的模板文本。
6. 未优化的循环
循环通常会导致 JS 中的性能问题。一个常见的错误是重复访问数组长度:
拉胯:
ini
const items = [/*...*/];
for (let i = 0; i < items.length; i++) {
// ...
}
冗余检查会抑制优化。更nice:.length
nice:
ini
const items = [/*...*/];
const len = items.length;
for (let i = 0; i < len; i++) {
// ...
}
缓存长度可提高速度。其他优化包括将不变量从循环中提升出来,简化终止条件,以及避免在迭代中执行昂贵的操作。
7. 不必要的同步操作
JS 的异步功能是一个关键优势。但要小心阻塞 I/O!例如:
拉胯:
ini
const data = fs.readFileSync('file.json'); // blocks!
这会在从磁盘读取时停止执行。请改用回调或承诺:
nice:
javascript
fs.readFile('file.json', (err, data) => {
// ...
});
现在,在读取文件时,事件循环将继续。对于复杂的流程,简化异步逻辑。避免同步操作,以防止阻塞。async/await
8. 阻塞事件循环
JavaScript 使用单线程事件循环。阻止它会使执行停止。一些常见的阻滞剂:
-
繁重的计算任务
-
同步 I/O
-
未优化的算法
例如:
scss
function countPrimes(max) {
// Unoptimized loop
for (let i = 0; i <= max; i++) {
// ...check if prime...
}
}
countPrimes(1000000); // Long running!
这将同步执行,阻止其他事件。为避免:
- 推迟不必要的工作
- 批量数据处理
- 使用工作线程
- 寻找优化机会
保持事件循环平稳运行。定期分析以捕获阻止代码。
9. 低效的错误处理
在 Js 中正确处理错误至关重要。但要注意性能隐患!
拉胯:
javascript
try {
// ...
} catch (err) {
console.error(err); // just logging
}
这会捕获错误,但不采取任何纠正措施。未经处理的错误通常会导致内存泄漏或数据损拉胯。
nice:
scss
try {
// ...
} catch (err) {
console.error(err);
// Emit error event
emitError(err);
// Nullify variables
obj = null;
// Inform user
showErrorNotice();
}
光有日志记录是不够的!清理项目、通知用户并考虑恢复选项。使用 Sentry 等工具监控生产中的错误。显式处理所有错误。
11. 内存泄漏
当内存被分配但从未释放时,就会发生内存泄漏。随着时间的流逝,泄漏会累积并降低性能。
Js 中的常见来源包括:
-
未清理的事件侦听器
-
对已删除 DOM 节点的过时引用
-
不再需要的缓存数据
-
闭包中的累积状态
例如:
scss
function processData() {
const data = [];
// Use closure to accumulate data
return function() {
data.push(getData());
}
}
const processor = processData();
// Long running...keeps holding reference to growing data array!
阵列不断变大,但从未被清除。要解决以下问题:
- 使用弱引用
- 清理事件侦听器
- 删除不再需要的引用
- 限制关闭状态大小
监控内存使用情况并观察增长趋势。在泄漏堆积之前主动消除泄漏。
11. 过度使用依赖关系
虽然 npm 提供了无穷无尽的选择,但请抵制过度导入的冲动!每个依赖项都会增加捆绑包大小和攻击面。
拉胯:
javascript
import _ from 'lodash';
import moment from 'moment';
import validator from 'validator';
// etc...
为次要实用程序导入整个库。最好根据需要挑选樱桃帮手:
nice:
python
import cloneDeep from 'lodash/cloneDeep';
import { format } from 'date-fns';
import { isEmail } from 'validator';
只导入你需要的内容。定期查看依赖项以修剪未使用的依赖项。保持捆绑包精简并最大限度地减少依赖关系。
12. 缓存不足
缓存允许通过重用先前的结果来跳过昂贵的计算。但它经常被忽视。
拉胯:
scss
function generateReport() {
//执行卡的一批的处理
//生成报表数据...
}
generateReport(); // Computes
generateReport(); // Computes again!
由于输入未更改,因此可以缓存报表:
nice:
csharp
let cachedReport;
function generateReport() {
if (cachedReport) {
return cachedReport;
}
cachedReport = // expensive processing...
return cachedReport;
}
现在,重复呼叫速度很快。其他缓存策略:
-
内存缓存,如 Redis
-
HTTP 缓存标头
-
用于客户端缓存的 LocalStorage
-
用于资产缓存的 CDN
识别缓存机会 - 它们通常提供很大的加速!
13. 未优化的数据库查询
与数据库连接时,低效的查询可能会降低性能。要避免的一些问题:
拉胯:
php
// 无索引
db.find({name: 'John', age: 35});
// 不必要的字段
db.find({first: 'John', last:'Doe', email:'john@doe.com'}, {first: 1, last: 1});
// 太多独立的查询
for (let id of ids) {
const user = db.find({id});
}
这无法利用索引,检索未使用的字段,并执行过多的查询。
nice:
php
// 在"name"上使用索引
db.find({name: 'John'}).hint({name: 1});
// 只有"email"字段
db.find({first: 'John'}, {email: 1});
// 在一个查询中获取用户
const users = db.find({
id: {$in: ids}
});
分析和解释计划。战略性地创建索引。避免多个零碎的查询。优化数据存储交互。
14. Promise 中的错误处理不当
Promise 简化了异步代码。但是,未经处理的拒绝是无声的失败!
拉胯:
scss
function getUser() {
return fetch('/user')
.then(r => r.json());
}
getUser();
如果拒绝,则不会注意到异常。相反:fetch
nice:
scss
function getUser() {
return fetch('/user')
.then(r => r.json())
.catch(err => console.error(err));
}
getUser();
链接可以正确处理错误。其他提示:.catch()
- 避免承诺嵌套地狱
- 在顶层处理拒绝
- 配置未处理的拒绝跟踪
不要忽视承诺错误!
15. 同步网络操作
网络请求应该是异步的。但有时会使用同步变体:
拉胯:
ini
const data = http.getSync('http://example.com/data'); // blocks!
这会在请求期间停止事件循环。请改用回调:
nice:
dart
http.get('http://example.com/data', res => {
// ...
});
或承诺:
ini
fetch('http://example.com/data')
.then(res => res.json())
.then(data => {
// ...
});
异步网络请求允许在等待响应时进行其他处理。避免同步网络调用。
16. 低效的文件 I/O 操作
同步读取/写入文件会阻塞。例如:
拉胯:
ini
const contents = fs.readFileSync('file.txt'); // blocks!
这会在磁盘 I/O 期间停止执行。相反:
nice:
javascript
fs.readFile('file.txt', (err, contents) => {
// ...
});
// or promises
fs.promises.readFile('file.txt')
.then(contents => {
// ...
});
这允许事件循环在文件读取期间继续。
对于多个文件,请使用流:
scss
function processFiles(files) {
for (let file of files) {
fs.createReadStream(file)
.pipe(/*...*/);
}
}
避免同步文件操作。使用回调、promise 和流。
17. 忽略性能分析和优化
在出现明显问题之前,很容易忽略性能。但优化应该持续进行!首先使用分析工具进行测量:
-
浏览器开发工具时间线
-
Node.js 分析器
-
第三方探查器
即使性能看起来不错,这也揭示了优化机会:
scss
// profile.js
function processOrders(orders) {
orders.forEach(o => {
// ...
});
}
processOrders(allOrders);
探查器显示需要 200 毫秒。我们调查并发现:processOrders
- 未优化的循环
- 昂贵的内部操作
- 不必要的工作
我们以迭代方式进行优化。最终版本需要 5 毫秒!
分析指导优化。制定绩效预算,超出预算失败。经常测量并明智地优化。
18. 不使用缓存机制
缓存通过避免重复工作来提高速度。但它经常被遗忘。
拉胯:
scss
// Compute expensive report
function generateReport() {
// ...heavy processing...
}
generateReport(); // Computes
generateReport(); // Computes again!
相同的输入始终产生相同的输出。我们应该缓存:
nice:
ini
// 缓存报告内容
const cache = {};
function generateReport() {
if (cache.report) {
return cache.report;
}
const report = // ...compute...
cache.report = report;
return report;
}
现在,重复呼叫速度很快。其他缓存策略:
- 内存缓存,如 Redis
- HTTP 缓存标头
- 用于客户端缓存的 LocalStorage
- 用于资产缓存的 CDN
识别缓存机会 - 它们通常会带来巨大的胜利!
19. 不必要的代码重复
重复的代码会损害可维护性和可优化性。考虑:
ini
function userStats(user) {
const name = user.name;
const email = user.email;
// ...logic...
}
function orderStats(order) {
const name = order.customerName;
const email = order.customerEmail;
// ...logic...
}
提取是重复的。我们重构:
scss
function getCustomerInfo(data) {
return {
name: data.name,
email: data.email
};
}
function userStats(user) {
const { name, email } = getCustomerInfo(user);
// ...logic...
}
function orderStats(order) {
const { name, email } = getCustomerInfo(order);
// ...logic...
}
现在,它只定义了一次。其他修复:
- 提取实用程序函数
- 生成帮助程序类
- 利用模块实现可重用性
尽可能删除重复。它改进了代码运行状况和优化。
20. 图片、http、压缩、打包等优化
图片和打包等方面的就不说了,那些都是老生常谈的,浅浅来一个http请求优化的:
如果同时请求一个数据非常大的接口,可能会造成服务崩溃,所以我们需要处理请求的频率
scss
/**
* 睡眠函数
* @param numberMillis 要睡眠的毫秒数
*/
function sleepHandle(numberMillis) {
let now = new Date();
let exitTime = now.getTime() + numberMillis;
while (true) {
now = new Date();
if (now.getTime() > exitTime)
return;
}
}
for(var i = 1; i < 5 ; i++){
ajax({
data:i,
success() {
sleepHandle(3000); // 每隔三秒请求一次
}
})
}
总结下
不管是防止软件运行卡的一批,还是防止量变上升到质变的屎山代码,优化 JS 应用程序性能是一个迭代过程,也是我们自我要求的一个过程吧,如果站在职业角度来说的话,这样可能会高尚一点点😂
作为我们前端开发人员,需要关注的关键领域包括最小化 DOM 更改、利用异步技术、消除阻塞操作、减少依赖关系、利用缓存和删除不必要的重复,做到高内聚低耦合。
凭借专注和经验,你可以发现瓶颈并针对特定工作负载进行优化。结果是更快、更精简、响应更灵敏的 Web 应用程序,对用户体验来说嘎嘎的。
因此,我们要坚持不懈地进行优化 - 使用这些技巧,让你写的代码飞起来!
好了,今天就说到这里吧。
感谢jym浏览本文,共同进步🤞,若有更好的观点,欢迎评论区讨论哈🌹。