緩慢或卡頓的網站是業餘愛好者的標誌,而流暢、優化的體驗會讓用戶感到高興,並使專業人士脫穎而出。
但創建真正高效能的網路應用程式充滿了陷阱。錯誤比比皆是,它們可能會減慢JavaScript的速度,而您甚至沒有意識到這一點。微小的疏忽會讓你的程式碼變得臃腫,並悄悄地一點一點地降低速度。
這是怎麼回事?
事實證明,我們有很多常見的方式會無意間減慢 JavaScript 的速度。隨著時間的推移,可能會阻礙網站效能。
這些錯誤是可以避免的。
今天,我們專注於可能會悄悄減慢 JavaScript 和 Node.js 應用程式速度的 19 個效能陷阱。我們將透過說明性範例和可操作的解決方案來探索導致這些問題的原因,以優化您的程式碼。
辨識並消除這些危害是打造讓使用者滿意的流暢網路體驗的關鍵。那麼,讓我們深入了解一下吧!
1. 不正確的變數宣告和作用域
第一次學習 JavaScript 時,很容易在全域宣告所有變數。然而,這會導致未來出現問題。讓我們來看一個例子:
// globals.js var color = 'blue'; function printColor() { console.log(color); } printColor(); // Prints 'blue'
這工作正常,但想像一下如果我們載入另一個腳本:
// script2.js var color = 'red'; printColor(); // Prints 'red'!
因為color是全域的,所以script2.js覆蓋了它!要解決此問題,請盡可能在函數內部宣告變數:
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);
這天真地插入到每個訊息上。最好是限制更新:
正確範例:
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); });
這會為每個刪除按鈕新增一個偵聽器。需要更好地使用事件委託:
正確範例:
const table = document.querySelector('table'); table.addEventListener('click', e => { if (e.target.classList.contains('delete')) { handleDelete(e); } });
現在,.net 上只有一個偵聽器,更少的記憶體開銷。
事件委託利用事件冒泡。一個偵聽器可以處理來自多個後代的事件。只要適用,就使用委派。
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. 未最佳化的迴圈
JavaScript 中的迴圈經常會導致效能問題。一個常見的錯誤是重複存取陣列長度:
反面範例:
const items = [/*...*/]; for (let i = 0; i < items.length; i++) { // ... }
冗餘檢查.length會抑制最佳化。
正確範例:
const items = [/*...*/]; const len = items.length; for (let i = 0; i < len; i++) { // ... }
快取長度可以提高速度。其他最佳化包括將不變量提升到循環之外、簡化終止條件以及避免迭代內昂貴的操作。
7. 不必要的同步運算
JavaScript 的异步功能是一个关键优势。但要小心阻塞 I/O!例如:
反面例子:
const data = fs.readFileSync('file.json'); // blocks!
这会在从磁盘读取时停止执行。相反,如果使用回调或承诺:
正确示例:
fs.readFile('file.json', (err, data) => { // ... });
现在,事件循环在读取文件时继续。对于复杂的流程,async/await简化异步逻辑。避免同步操作以防止阻塞。
8. 阻止事件循环
JavaScript 使用单线程事件循环。阻止它会停止执行。一些常见的拦截器:
繁重的计算任务
同步输入/输出
未优化的算法
例如:
function countPrimes(max) { // Unoptimized loop for (let i = 0; i <= max; i++) { // ...check if prime... } } countPrimes(1000000); // Long running!
这会同步执行,并阻止其他事件。避免:
推迟不必要的工作
批量数据处理
使用工作线程
寻找优化机会
保持事件循环顺利运行。定期分析以捕获阻塞代码。
9. 错误处理效率低下
在 JavaScript 中正确处理错误至关重要。但要小心性能陷阱!
反面例子:
try { // ... } catch (err) { console.error(err); // just logging }
这会捕获错误但不采取纠正措施。未处理的错误通常会导致内存泄漏或数据损坏。
正确示例:
try { // ... } catch (err) { console.error(err); // Emit error event emitError(err); // Nullify variables obj = null; // Inform user showErrorNotice(); }
记录还不够!清理工件、通知用户并考虑恢复选项。使用 Sentry 等工具来监控生产中的错误。明确处理所有错误。
10. 内存泄漏
当内存被分配但从未释放时,就会发生内存泄漏。随着时间的推移,泄漏会累积并降低性能。
JavaScript 中的常见来源包括:
未清理的事件监听器
对已删除 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...
为次要实用程序导入整个库。最好根据需要挑选助手:
正确示例:
import cloneDeep from 'lodash/cloneDeep'; import { format } from 'date-fns'; import { isEmail } from 'validator';
只导入您需要的内容。定期检查依赖关系以删除未使用的依赖关系。保持捆绑精简并最大限度地减少依赖性。
12. 缓存不足
缓存允许通过重用先前的结果来跳过昂贵的计算。但它经常被忽视。
反面例子:
function generateReport() { // Perform expensive processing // to generate report data... } generateReport(); // Computes generateReport(); // Computes again!
由于输入没有更改,因此可以缓存报告:
正确示例:
let cachedReport; function generateReport() { if (cachedReport) { return cachedReport; } cachedReport = // expensive processing... return cachedReport; }
现在,重复调用速度很快。
13. 未优化的数据库查询
与数据库交互时,低效的查询可能会降低性能。需要避免的一些问题:
反面例子:
// No indexing db.find({name: 'John', age: 35}); // Unecessary fields db.find({first: 'John', last:'Doe', email:'john@doe.com'}, {first: 1, last: 1}); // Too many separate queries for (let id of ids) { const user = db.find({id}); }
这无法利用索引、检索未使用的字段并执行过多的查询。
正确示例:
// Use index on 'name' db.find({name: 'John'}).hint({name: 1}); // Only get 'email' field db.find({first: 'John'}, {email: 1}); // Get users in one query const users = db.find({ id: {$in: ids} });
分析并解释计划。战略性地创建索引。避免多次零散的查询。优化数据存储交互。
14. Promise 中错误处理不当
Promise 简化了异步代码。但未经处理的拒绝就是无声的失败!
反面例子:
function getUser() { return fetch('/user') .then(r => r.json()); } getUser();
如果fetch拒绝,异常就不会被注意到。
正确示例:
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!
这会在请求期间停止事件循环。相反,使用回调:
正确示例:
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 期间停止执行。
正确示例:
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);
分析器显示processOrders需要 200 毫秒。
分析指导优化。制定绩效预算,如果超出则失败。经常测量并明智地优化。
18. 不利用缓存机制
缓存通过避免重复工作来提高速度。但它经常被遗忘。
反面例子:
// Compute expensive report function generateReport() { // ...heavy processing... } generateReport(); // Computes generateReport(); // Computes again!
相同的输入总是产生相同的输出。我们应该缓存:
正确示例:
// Cache report contents const cache = {}; function generateReport() { if (cache.report) { return cache.report; } const report = // ...compute... cache.report = report; return report; }
现在,重复调用速度很快。
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... }
现在,它只定义一次。
结论
优化 JavaScript 应用程序性能是一个迭代过程。通过学习有效的实践并勤于分析,可以显着提高速度。
需要关注的关键领域包括最大限度地减少 DOM 更改、利用异步技术、消除阻塞操作、减少依赖性、利用缓存以及删除不需要的重复。
以上是程式碼運行慢?避免這19個常見的JavaScript和Node.js錯誤,讓你的程式高速狂飆的詳細內容。更多資訊請關注PHP中文網其他相關文章!