緩慢或卡頓的網(wǎng)站是業(yè)余愛好者的標志,而流暢、優(yōu)化的體驗會讓用戶感到高興,并使專業(yè)人士脫穎而出。
但創(chuàng)建真正高性能的網(wǎng)絡應用程序充滿了陷阱。錯誤比比皆是,它們可能會拖慢JavaScript的速度,而您甚至沒有意識到這一點。微小的疏忽會讓你的代碼變得臃腫,并悄悄地一點一點地降低速度。
這是怎么回事?
事實證明,我們有很多常見的方式會無意中減慢 JavaScript 的速度。隨著時間的推移,可能會阻礙網(wǎng)站性能。
這些錯誤是可以避免的。
今天,我們重點關注可能會悄悄減慢 JavaScript 和 Node.js 應用程序速度的 19 個性能陷阱。我們將通過說明性示例和可操作的解決方案來探索導致這些問題的原因,以優(yōu)化您的代碼。
識別并消除這些危害是打造讓用戶滿意的流暢網(wǎng)絡體驗的關鍵。那么,讓我們深入了解一下吧!
1. 不正確的變量聲明和作用域
第一次學習 JavaScript 時,很容易在全局聲明所有變量。然而,這會導致未來出現(xiàn)問題。讓我們看一個例子:
// globals.js var color = 'blue'; function printColor() { console.log(color); } printColor(); // Prints 'blue'
登錄后復制
這工作正常,但想象一下如果我們加載另一個腳本:
// script2.js var color = 'red'; printColor(); // Prints 'red'!
登錄后復制
因為color是全局的,所以script2.js覆蓋了它!要解決此問題,請盡可能在函數(shù)內部聲明變量:
function printColor() { var color = 'blue'; // local variable console.log(color); } printColor(); // Prints 'blue'
登錄后復制
現(xiàn)在,其他腳本的更改不會影響printColor.
不必要時在全局范圍內聲明變量是一種反模式。嘗試將全局變量限制為配置常量。對于其他變量,請在盡可能小的范圍內進行本地聲明。
2. 低效的 DOM 操作
更新 DOM 元素時,批量更改而不是一次操作一個節(jié)點。考慮這個例子:
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);
登錄后復制
現(xiàn)在,我們最多每 100 毫秒更新一次,從而保持 DOM 操作較低。
對于高度動態(tài)的 UI,請考慮像 React 這樣的虛擬 DOM 庫。這些最大限度地減少了使用虛擬表示的 DOM 操作。
4.缺乏活動委托
將事件偵聽器附加到許多元素會產(chǎn)生不必要的開銷。考慮一個每行都有刪除按鈕的表:
反面例子:
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); } });
登錄后復制
現(xiàn)在,.net 上只有一個偵聽器,更少的內存開銷。
事件委托利用事件冒泡。一個偵聽器可以處理來自多個后代的事件。只要適用,就使用委派。
5. 低效的字符串連接
在循環(huán)中連接字符串時,性能會受到影響。考慮這段代碼:
let html = ''; for (let i = 0; i < 10; i++) { html += '<div>' + i + '</div>'; }
登錄后復制
創(chuàng)建新字符串需要分配內存。最好使用數(shù)組:
const parts = []; for (let i = 0; i < 10; i++) { parts.push('<div>', i, '</div>'); } const html = parts.join('');
登錄后復制
構建數(shù)組可以最大限度地減少中間字符串。.join()最后連接一次。
對于多個字符串添加,請使用數(shù)組連接。另外,請考慮嵌入值的模板文字。
6. 未優(yōu)化的循環(huán)
JavaScript 中的循環(huán)經(jīng)常會導致性能問題。一個常見的錯誤是重復訪問數(shù)組長度:
反面例子:
const items = [/*...*/]; for (let i = 0; i < items.length; i++) { // ... }
登錄后復制
冗余檢查.length會抑制優(yōu)化。
正確示例:
const items = [/*...*/]; const len = items.length; for (let i = 0; i < len; i++) { // ... }
登錄后復制
緩存長度可以提高速度。其他優(yōu)化包括將不變量提升到循環(huán)之外、簡化終止條件以及避免迭代內昂貴的操作。
7. 不必要的同步操作
JavaScript 的異步功能是一個關鍵優(yōu)勢。但要小心阻塞 I/O!例如:
反面例子:
const data = fs.readFileSync('file.json'); // blocks!
登錄后復制
這會在從磁盤讀取時停止執(zhí)行。相反,如果使用回調或承諾:
正確示例:
fs.readFile('file.json', (err, data) => { // ... });
登錄后復制
現(xiàn)在,事件循環(huán)在讀取文件時繼續(xù)。對于復雜的流程,async/await簡化異步邏輯。避免同步操作以防止阻塞。
8. 阻止事件循環(huán)
JavaScript 使用單線程事件循環(huán)。阻止它會停止執(zhí)行。一些常見的攔截器:
繁重的計算任務
同步輸入/輸出
未優(yōu)化的算法
例如:
function countPrimes(max) { // Unoptimized loop for (let i = 0; i <= max; i++) { // ...check if prime... } } countPrimes(1000000); // Long running!
登錄后復制
這會同步執(zhí)行,并阻止其他事件。避免:
推遲不必要的工作
批量數(shù)據(jù)處理
使用工作線程
尋找優(yōu)化機會
保持事件循環(huán)順利運行。定期分析以捕獲阻塞代碼。
9. 錯誤處理效率低下
在 JavaScript 中正確處理錯誤至關重要。但要小心性能陷阱!
反面例子:
try { // ... } catch (err) { console.error(err); // just logging }
登錄后復制
這會捕獲錯誤但不采取糾正措施。未處理的錯誤通常會導致內存泄漏或數(shù)據(jù)損壞。
正確示例:
try { // ... } catch (err) { console.error(err); // Emit error event emitError(err); // Nullify variables obj = null; // Inform user showErrorNotice(); }
登錄后復制
記錄還不夠!清理工件、通知用戶并考慮恢復選項。使用 Sentry 等工具來監(jiān)控生產(chǎn)中的錯誤。明確處理所有錯誤。
10. 內存泄漏
當內存被分配但從未釋放時,就會發(fā)生內存泄漏。隨著時間的推移,泄漏會累積并降低性能。
JavaScript 中的常見來源包括:
未清理的事件監(jiān)聽器
對已刪除 DOM 節(jié)點的過時引用
不再需要的緩存數(shù)據(jù)
閉包中的累積狀態(tài)
例如:
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!
登錄后復制
數(shù)組不斷變大,但從未被清除。修理:
使用弱引用
清理事件監(jiān)聽器
刪除不再需要的引用
限制關閉狀態(tài)大小
監(jiān)視內存使用情況并觀察增長趨勢。在泄漏堆積之前主動消除泄漏。
11. 過度使用依賴項
雖然 npm 提供了無窮無盡的選擇,但請抵制過度導入的沖動!每個依賴項都會增加包大小和攻擊面。
反面例子:
import _ from 'lodash'; import moment from 'moment'; import validator from 'validator'; // etc...
登錄后復制
為次要實用程序導入整個庫。最好根據(jù)需要挑選助手:
正確示例:
import cloneDeep from 'lodash/cloneDeep'; import { format } from 'date-fns'; import { isEmail } from 'validator';
登錄后復制
只導入您需要的內容。定期檢查依賴關系以刪除未使用的依賴關系。保持捆綁精簡并最大限度地減少依賴性。
12. 緩存不足
緩存允許通過重用先前的結果來跳過昂貴的計算。但它經(jīng)常被忽視。
反面例子:
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; }
登錄后復制
現(xiàn)在,重復調用速度很快。
13. 未優(yōu)化的數(shù)據(jù)庫查詢
與數(shù)據(jù)庫交互時,低效的查詢可能會降低性能。需要避免的一些問題:
反面例子:
// 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}); }
登錄后復制
這無法利用索引、檢索未使用的字段并執(zhí)行過多的查詢。
正確示例:
// 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} });
登錄后復制
分析并解釋計劃。戰(zhàn)略性地創(chuàng)建索引。避免多次零散的查詢。優(yōu)化數(shù)據(jù)存儲交互。
14. Promise 中錯誤處理不當
Promise 簡化了異步代碼。但未經(jīng)處理的拒絕就是無聲的失敗!
反面例子:
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. 同步網(wǎng)絡操作
網(wǎng)絡請求應該是異步的。但有時會使用同步變體:
反面例子:
const data = http.getSync('http://example.com/data'); // blocks!
登錄后復制
這會在請求期間停止事件循環(huán)。相反,使用回調:
正確示例:
http.get('http://example.com/data', res => { // ... });
登錄后復制
或者:
fetch('http://example.com/data') .then(res => res.json()) .then(data => { // ... });
登錄后復制
異步網(wǎng)絡請求允許在等待響應時進行其他處理。避免同步網(wǎng)絡調用。
16. 低效的文件 I/O 操作
讀/寫文件同步阻塞。例如:
反面例子:
const contents = fs.readFileSync('file.txt'); // blocks!
登錄后復制
這會在磁盤 I/O 期間停止執(zhí)行。
正確示例:
fs.readFile('file.txt', (err, contents) => { // ... }); // or promises fs.promises.readFile('file.txt') .then(contents => { // ... });
登錄后復制
這允許事件循環(huán)在文件讀取期間繼續(xù)。
對于多個文件,使用流:
function processFiles(files) { for (let file of files) { fs.createReadStream(file) .pipe(/*...*/); } }
登錄后復制
避免同步文件操作。使用回調、promise 和流。
17. 忽略性能分析和優(yōu)化
在出現(xiàn)明顯問題之前,很容易忽視性能。但優(yōu)化應該持續(xù)進行!首先使用分析工具進行測量:
瀏覽器開發(fā)工具時間線
Node.js 分析器
第三方分析器
即使性能看起來不錯,這也揭示了優(yōu)化機會:
// profile.js function processOrders(orders) { orders.forEach(o => { // ... }); } processOrders(allOrders);
登錄后復制
分析器顯示processOrders需要 200 毫秒。
分析指導優(yōu)化。制定績效預算,如果超出則失敗。經(jīng)常測量并明智地優(yōu)化。
18. 不利用緩存機制
緩存通過避免重復工作來提高速度。但它經(jīng)常被遺忘。
反面例子:
// Compute expensive report function generateReport() { // ...heavy processing... } generateReport(); // Computes generateReport(); // Computes again!
登錄后復制
相同的輸入總是產(chǎn)生相同的輸出。我們應該緩存:
正確示例:
// Cache report contents const cache = {}; function generateReport() { if (cache.report) { return cache.report; } const report = // ...compute... cache.report = report; return report; }
登錄后復制
現(xiàn)在,重復調用速度很快。
19. 不必要的代碼重復
重復的代碼會損害可維護性和可優(yōu)化性。
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... }
登錄后復制
現(xiàn)在,它只定義一次。
結論
優(yōu)化 JavaScript 應用程序性能是一個迭代過程。通過學習有效的實踐并勤于分析,可以顯著提高速度。
需要關注的關鍵領域包括最大限度地減少 DOM 更改、利用異步技術、消除阻塞操作、減少依賴性、利用緩存以及刪除不需要的重復。
以上就是代碼運行慢?避免這19個常見的JavaScript和Node.js錯誤,讓你的程序高速狂飆的詳細內容,更多請關注www.92cms.cn其它相關文章!