likes
comments
collection
share

【性能】你的代码慢吗?:避免这 20 个常见的坑点

作者站长头像
站长
· 阅读数 3

Jym好😘,我是珑墨,今天给大家分享  如何避免拖慢js执行速度常见的坑点 ,嘎嘎的😍,看下面

软件运行速度、性能及用户体验。这在 咱Web 开发领域占据主导地位,尤其是 Js 和 Node。缓慢或卡顿的网站是业余爱好者的标志(这么说你不会怼我吧😂),而流畅、优化的体验会让用户感到高兴,这就叫专业。

但是,做到真正高性能的 Web 应用程序充满了陷阱。坑点一抓一大把,这些陷阱可能会在你不知不觉中拖累 Js 的步伐。微小的疏忽会使你的代码膨胀并质变地拖慢运行速度,最终可能成为屎山代码!😂

有时候,你警惕地缩小你的代码并利用缓存、打包等等......然而,你的网站有时仍然感觉异常缓慢,甚至卡的一批,浏览器都给整崩了。UI 在滚动或单击按钮时断断续续。页面需要亿万年的时间才能加载,这时候用户就骂骂咧咧的走了,从而导致用户体验差而流失用户,我擦,这就很...😂

事实证明,我们有许多常见的方法会无意中减慢 Js 的速度。随着时间的推移,反模式可能会阻碍网站性能。

今天,我们将重点介绍 19 大性能陷阱,这些陷阱可能会悄悄地减慢 Js 应用程序的速度。我们将通过说明性示例和可操作的解决方案来探索导致它们的原因,以优化你的代码。

识别和消除这些危害是打造令用户满意的流畅 Web 体验的关键。所以,让我们嘎嘎开始吧!😎

1. 不正确的变量声明和范围

我们都深有所感,第一次学习 Js 时,很想全局声明所有变量。但是,这会导致未来的问题。让我们看一个例子:

// globals.js

var color = 'blue';

function printColor() {
  console.log(color); 
}

printColor(); // Prints 'blue'

这很好用,但想象一下,如果我们加载了另一个脚本:

// script2.js

var color = 'red';

printColor(); // Prints 'red'!

因为是全局的,script2.js 覆盖了它!要解决此问题,请尽可能在函数中本地声明变量:color

function printColor() {
  var color = 'blue'; // local variable
  
  console.log(color);
}

printColor(); // Prints 'blue'

现在,其他脚本中的更改不会影响 .printColor

在不必要时在全局范围内声明变量是一种反模式。尝试将全局变量限制为配置常量。对于其他变量,请在尽可能小的范围内本地声明它们。

2. 低效的 DOM 操作

更新 DOM 元素时,批量更改,而不是一次操作一个节点。请看这个例子:

const ul = document.getElementById('list');

for (let i = 0; i < 10; i++) {
  const li = document.createElement('li');
  li.textContent = i;
  
  ul.appendChild(li);
}

这将逐个追加列表项。最好先构建一个字符串,然后再设置:.innerHTML

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更新会降低性能。考虑一个将消息插入页面的聊天应用程序

拉胯:

// New message received
const msg = `<div>${messageText}</div>`;
chatLog.insertAdjacentHTML('beforeend', msg);

这简单地插入到每条消息上。最好限制更新

nice:

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. 缺乏事件事件委派

将事件侦听器附加到许多元素会产生不必要的开销。考虑一个每行都有删除按钮的表:

拉胯:

const rows = document.querySelectorAll('table tr');

rows.forEach(row => {
  const deleteBtn = row.querySelector('.delete');  

  deleteBtn.addEventListener('click', handleDelete);
});

这将为每个删除按钮添加一个侦听器。最好使用事件委派:

nice:

const table = document.querySelector('table');

table.addEventListener('click', e => {
  if (e.target.classList.contains('delete')) {
    handleDelete(e);
  }
});

现在,.更少的内存开销。<table>

事件委派利用事件冒泡。一个侦听器可以处理来自多个后代的事件。如果适用,请使用委派。

5. 低效的字符串连接

在循环中连接字符串时,性能会受到影响。请考虑以下代码:

let html = '';

for (let i = 0; i < 10; i++) {
  html += '<div>' + i + '</div>';
}

创建新字符串需要内存分配。最好使用数组:

const parts = [];

for (let i = 0; i < 10; i++) {
  parts.push('<div>', i, '</div>');
}

const html = parts.join('');

构建数组可以最大程度地减少中间字符串。 在末尾连接一次。.join()

对于多个字符串添加,请使用数组联接。此外,请考虑嵌入值的模板文本。

6. 未优化的循环

循环通常会导致 JS 中的性能问题。一个常见的错误是重复访问数组长度:

拉胯:

const items = [/*...*/];

for (let i = 0; i < items.length; i++) {
  // ...
}

冗余检查会抑制优化。更nice:.length

nice:

const items = [/*...*/];  
const len = items.length;

for (let i = 0; i < len; i++) {
  // ...
} 

缓存长度可提高速度。其他优化包括将不变量从循环中提升出来,简化终止条件,以及避免在迭代中执行昂贵的操作。

7. 不必要的同步操作

JS 的异步功能是一个关键优势。但要小心阻塞 I/O!例如:

拉胯:

const data = fs.readFileSync('file.json'); // blocks!

这会在从磁盘读取时停止执行。请改用回调或承诺:

nice:

fs.readFile('file.json', (err, data) => {
  // ...
});

现在,在读取文件时,事件循环将继续。对于复杂的流程,简化异步逻辑。避免同步操作,以防止阻塞。async/await

8. 阻塞事件循环

JavaScript 使用单线程事件循环。阻止它会使执行停止。一些常见的阻滞剂:

  • 繁重的计算任务

  • 同步 I/O

  • 未优化的算法

例如:

function countPrimes(max) {
  // Unoptimized loop
  for (let i = 0; i <= max; i++) {
    // ...check if prime...
  }
}

countPrimes(1000000); // Long running!

这将同步执行,阻止其他事件。为避免:

  • 推迟不必要的工作
  • 批量数据处理
  • 使用工作线程
  • 寻找优化机会

保持事件循环平稳运行。定期分析以捕获阻止代码。

9. 低效的错误处理

在 Js 中正确处理错误至关重要。但要注意性能隐患!

拉胯:

try {
  // ...
} catch (err) {
  console.error(err); // just logging
}

这会捕获错误,但不采取任何纠正措施。未经处理的错误通常会导致内存泄漏或数据损拉胯。

nice:

try {
  // ...
} catch (err) {
  console.error(err);
  
  // Emit error event 
  emitError(err); 
  
  // Nullify variables
  obj = null;
  
  // Inform user
  showErrorNotice();
}

光有日志记录是不够的!清理项目、通知用户并考虑恢复选项。使用 Sentry 等工具监控生产中的错误。显式处理所有错误。

11. 内存泄漏

当内存被分配但从未释放时,就会发生内存泄漏。随着时间的流逝,泄漏会累积并降低性能。

Js 中的常见来源包括:

  • 未清理的事件侦听器

  • 对已删除 DOM 节点的过时引用

  • 不再需要的缓存数据

  • 闭包中的累积状态

例如:

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 提供了无穷无尽的选择,但请抵制过度导入的冲动!每个依赖项都会增加捆绑包大小和攻击面。

拉胯:

import _ from 'lodash';
import moment from 'moment'; 
import validator from 'validator';
// etc...

为次要实用程序导入整个库。最好根据需要挑选樱桃帮手:

nice:

import cloneDeep from 'lodash/cloneDeep';
import { format } from 'date-fns';
import { isEmail } from 'validator'; 

只导入你需要的内容。定期查看依赖项以修剪未使用的依赖项。保持捆绑包精简并最大限度地减少依赖关系。

12. 缓存不足

缓存允许通过重用先前的结果来跳过昂贵的计算。但它经常被忽视。

拉胯:

function generateReport() {
  //执行卡的一批的处理
//生成报表数据…
}

generateReport(); // Computes
generateReport(); // Computes again!

由于输入未更改,因此可以缓存报表:

nice:

let cachedReport;

function generateReport() {
  if (cachedReport) {
    return cachedReport;
  }

  cachedReport = // expensive processing...
  return cachedReport; 
}

现在,重复呼叫速度很快。其他缓存策略:

  • 内存缓存,如 Redis

  • HTTP 缓存标头

  • 用于客户端缓存的 LocalStorage

  • 用于资产缓存的 CDN

识别缓存机会 - 它们通常提供很大的加速!

13. 未优化的数据库查询

与数据库连接时,低效的查询可能会降低性能。要避免的一些问题:

拉胯:

// 无索引
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:

// 在“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 简化了异步代码。但是,未经处理的拒绝是无声的失败!

拉胯:

function getUser() {
  return fetch('/user')
    .then(r => r.json()); 
}

getUser();

如果拒绝,则不会注意到异常。相反:fetch

nice:

function getUser() {
  return fetch('/user')
    .then(r => r.json())
    .catch(err => console.error(err));
} 

getUser();

链接可以正确处理错误。其他提示:.catch()

  • 避免承诺嵌套地狱
  • 在顶层处理拒绝
  • 配置未处理的拒绝跟踪

不要忽视承诺错误!

15. 同步网络操作

网络请求应该是异步的。但有时会使用同步变体:

拉胯:

const data = http.getSync('http://example.com/data'); // blocks!

这会在请求期间停止事件循环。请改用回调:

nice:

http.get('http://example.com/data', res => {
  // ...
});

或承诺:

fetch('http://example.com/data')
  .then(res => res.json())
  .then(data => {
    // ...
  });

异步网络请求允许在等待响应时进行其他处理。避免同步网络调用。

16. 低效的文件 I/O 操作

同步读取/写入文件会阻塞。例如:

拉胯:

const contents = fs.readFileSync('file.txt'); // blocks!

这会在磁盘 I/O 期间停止执行。相反:

nice:

fs.readFile('file.txt', (err, contents) => {
  // ...
});

// or promises

fs.promises.readFile('file.txt')
   .then(contents => {
     // ...  
   });

这允许事件循环在文件读取期间继续。

对于多个文件,请使用流:

function processFiles(files) {
  for (let file of files) {
    fs.createReadStream(file)
      .pipe(/*...*/);
  }
} 

避免同步文件操作。使用回调、promise 和流。

17. 忽略性能分析和优化

在出现明显问题之前,很容易忽略性能。但优化应该持续进行!首先使用分析工具进行测量:

  • 浏览器开发工具时间线

  • Node.js 分析器

  • 第三方探查器

即使性能看起来不错,这也揭示了优化机会:

// profile.js
function processOrders(orders) {
  orders.forEach(o => {
    // ...
  });
}

processOrders(allOrders);

探查器显示需要 200 毫秒。我们调查并发现:processOrders

  • 未优化的循环
  • 昂贵的内部操作
  • 不必要的工作

我们以迭代方式进行优化。最终版本需要 5 毫秒!

分析指导优化。制定绩效预算,超出预算失败。经常测量并明智地优化。

18. 不使用缓存机制

缓存通过避免重复工作来提高速度。但它经常被遗忘。

拉胯:

// Compute expensive report
function generateReport() {
  // ...heavy processing...
}

generateReport(); // Computes
generateReport(); // Computes again!

相同的输入始终产生相同的输出。我们应该缓存:

nice:

// 缓存报告内容
const cache = {};

function generateReport() {
  if (cache.report) {
    return cache.report;
  }

  const report = // ...compute...

  cache.report = report;
  return report;
}

现在,重复呼叫速度很快。其他缓存策略:

  • 内存缓存,如 Redis
  • HTTP 缓存标头
  • 用于客户端缓存的 LocalStorage
  • 用于资产缓存的 CDN

识别缓存机会 - 它们通常会带来巨大的胜利!

19. 不必要的代码重复

重复的代码会损害可维护性和可优化性。考虑:

function userStats(user) {
  const name = user.name;
  const email = user.email;
  
  // ...logic...
}

function orderStats(order) {
  const name = order.customerName;
  const email = order.customerEmail;

  // ...logic... 
}

提取是重复的。我们重构:

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请求优化的:

如果同时请求一个数据非常大的接口,可能会造成服务崩溃,所以我们需要处理请求的频率

/**
 *  睡眠函数
 *  @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浏览本文,共同进步🤞,若有更好的观点,欢迎评论区讨论哈🌹。