自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

代碼速度慢?避免這 19 種常見(jiàn)的 JavaScript 和 Node.js 錯(cuò)誤

譯文 精選
開發(fā) 前端
速度、性能和響應(yīng)性在 Web 開發(fā)中起著至關(guān)重要的作用,尤其是在使用 JavaScript 和 Node.js 開發(fā)時(shí)尤為重要。

譯者 | 劉汪洋

審校 | 重樓

速度、性能和響應(yīng)性在 Web 開發(fā)中起著至關(guān)重要的作用,尤其是在使用 JavaScript 和 Node.js 開發(fā)時(shí)尤為重要。如果一個(gè)網(wǎng)站響應(yīng)緩慢或界面卡頓,就會(huì)讓人感到非常業(yè)余;如果網(wǎng)站經(jīng)過(guò)精心設(shè)計(jì)和優(yōu)化,能夠給用戶帶來(lái)絲滑的使用體驗(yàn),就顯得非常專業(yè)。

打造一個(gè)真正高性能的 Web 應(yīng)用并非易事,其中潛藏著許多容易忽視的性能陷阱。這些不易被察覺(jué)的編程習(xí)慣或錯(cuò)誤不僅可能降低 JavaScript 的執(zhí)行速度,還可能導(dǎo)致代碼冗余,進(jìn)一步降低應(yīng)用的運(yùn)行效率。

即使你已經(jīng)壓縮了代碼并合理地使用了緩存,網(wǎng)站在某些情況下仍然可能運(yùn)行緩慢。例如,用戶界面在滾動(dòng)或點(diǎn)擊按鈕時(shí)出現(xiàn)明顯的卡頓,或頁(yè)面加載時(shí)間過(guò)長(zhǎng)。

這到底是為什么呢?

分析表明,有多種常見(jiàn)的不良實(shí)踐會(huì)無(wú)意中拖慢 JavaScript 的執(zhí)行效率。隨著時(shí)間推移,這些不良實(shí)踐會(huì)逐步影響網(wǎng)站的整體性能。

幸運(yùn)的是,這些性能問(wèn)題都是可以預(yù)防和解決的。

本文將深入分析 19 個(gè)可能降低 JavaScript 和 Node.js 應(yīng)用性能的隱性問(wèn)題,并通過(guò)具體的實(shí)例和解決策略,對(duì)這些問(wèn)題進(jìn)行詳細(xì)討論和優(yōu)化。

準(zhǔn)確地識(shí)別并解決這些性能陷阱是實(shí)現(xiàn)高效、流暢用戶體驗(yàn)的關(guān)鍵。下面,就讓我們一起探索吧!

1. 變量聲明與作用域的不恰當(dāng)使用

在 JavaScript 初學(xué)階段,開發(fā)者常常習(xí)慣性地在全局作用域中聲明變量,但這種做法往往會(huì)帶來(lái)很多副作用。
讓我們通過(guò)一個(gè)示例來(lái)解釋:

// globals.js

var color = 'blue';

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

printColor(); // 輸出 'blue'

雖然上述代碼看著沒(méi)有問(wèn)題,但如果有另一個(gè)腳本中編寫如下代碼:

// script2.js

var color = 'red';

printColor(); // 輸出 'red'!

由于 color 變量是在全局作用域內(nèi)定義的,因此 script2.js 就能夠輕易覆蓋它。為避免這種情況,應(yīng)盡量在函數(shù)作用域內(nèi)聲明局部變量:

function printColor() {
  var color = 'blue'; // 局部變量
  
  console.log(color);
}

printColor(); // 輸出 'blue'

這樣一來(lái),其他腳本中對(duì)全局變量的更改不會(huì)影響 printColor 函數(shù)。

在全局作用域內(nèi)隨意聲明變量是一種不良的編程習(xí)慣,也被認(rèn)為是編程反模式。應(yīng)當(dāng)盡量將全局變量的使用局限于配置常量,并確保其他變量在盡可能小的作用域內(nèi)被聲明。

2. DOM 操作的效率問(wèn)題

在更新 DOM 元素時(shí),建議批量更改,而不是一次只操作一個(gè)節(jié)點(diǎn)。
請(qǐng)看以下逐個(gè)添加列表項(xiàng)的代碼示例:

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

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

更好的做法是先構(gòu)建一個(gè)字符串,然后通過(guò) .innerHTML進(jìn)行設(shè)置:

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

for (let i = 0; i < 10; i++) {
  html += `<li>${i}</li>`; 
}

ul.innerHTML = html;

構(gòu)建字符串可以減少回流。我們只需一次更新 DOM,而不是十次。

對(duì)于多次更新,可以先集中收集所有的更改,然后一次性地應(yīng)用。或者更好的辦法是使用 DocumentFragment 進(jìn)行批量節(jié)點(diǎn)添加。

3. DOM 操作的過(guò)度使用

過(guò)于頻繁地更新 DOM 會(huì)對(duì)性能產(chǎn)生嚴(yán)重影響。
以一個(gè)聊天應(yīng)用為例,該應(yīng)用將每條新消息插入到頁(yè)面中。

不推薦的做法:

// 收到新消息
const msg = `<div>${messageText}</div>`;
chatLog.insertAdjacentHTML('beforeend', msg);

這種方法會(huì)在每收到一條新消息時(shí)直接進(jìn)行 DOM 插入。
一個(gè)更加高效的方法是限制更新的頻率:

推薦的做法:

let chatLogHTML = '';
const throttleTime = 100; // ms

// 收到新消息
chatLogHTML += `<div>${messageText}</div>`;

// 對(duì) DOM 更新進(jìn)行節(jié)流
setTimeout(() => {
  chatLog.innerHTML = chatLogHTML;
  chatLogHTML = ''; 
}, throttleTime);

通過(guò)這種方式,我們最多每 100ms 執(zhí)行一次 DOM 更新,有效地控制了更新操作的頻率。

對(duì)于高度動(dòng)態(tài)的用戶界面,可考慮使用如 React 等支持虛擬 DOM 的庫(kù)。這些庫(kù)能通過(guò)虛擬表示來(lái)最小化實(shí)際的 DOM 操作。

4. 事件委托的缺失

給多個(gè)元素分別添加事件監(jiān)聽(tīng)器會(huì)導(dǎo)致不必要的資源消耗。例如,一個(gè)表格,其中每一行都有一個(gè)刪除按鈕:

不推薦的實(shí)踐:

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

rows.forEach(row => {
  const deleteBtn = row.querySelector('.delete');
  
  deleteBtn.addEventListener('click', handleDelete);
});

這種方式為每一個(gè)刪除按鈕都單獨(dú)設(shè)置了一個(gè)事件監(jiān)聽(tīng)器。更優(yōu)的做法是采用事件委托機(jī)制:

推薦的實(shí)踐:

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

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

采用這種方式后,整個(gè) <table> 元素僅需一個(gè)事件監(jiān)聽(tīng)器,從而降低了內(nèi)存消耗。

事件委托是通過(guò)利用事件冒泡,讓單一的事件監(jiān)聽(tīng)器管理多個(gè)事件。應(yīng)該在合適的情況下使用委托。

5. 字符串拼接的低效性

在循環(huán)中進(jìn)行字符串拼接會(huì)影響性能。
看看下面的代碼:

let html = '';

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

創(chuàng)建新字符串需要內(nèi)存分配。為了提高性能,更有效的做法是使用數(shù)組:

const parts = [];

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

const html = parts.join('');

使用數(shù)組能減少中間字符串的生成。.join() 方法在最后進(jìn)行一次性的拼接。

對(duì)于多次字符串拼接,使用數(shù)組的 join 方法。同時(shí),也可以考慮使用模板字面量來(lái)嵌入變量。

6. 循環(huán)性能未經(jīng)優(yōu)化

在 JavaScript 中,循環(huán)常常是性能問(wèn)題的罪魁禍?zhǔn)住R粋€(gè)常見(jiàn)的錯(cuò)誤是反復(fù)獲取數(shù)組長(zhǎng)度:

不佳實(shí)踐:

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

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

重復(fù)檢查 .length 會(huì)阻礙優(yōu)化。

優(yōu)秀實(shí)踐:

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

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

通過(guò)緩存數(shù)組長(zhǎng)度,我們可以避免在每次迭代中都去計(jì)算它,從而提高循環(huán)速度。其他優(yōu)化手段包括從循環(huán)中提升不變量,簡(jiǎn)化終止條件,以及避免在迭代中進(jìn)行耗時(shí)較長(zhǎng)的操作。

7. 不必要的同步操作

JavaScript 的異步能力是其一大優(yōu)點(diǎn)。但要警惕阻塞式 I/O!

例如:

不佳實(shí)踐:

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

這會(huì)在從磁盤讀取數(shù)據(jù)時(shí)暫停執(zhí)行。相反,應(yīng)使用回調(diào)或 Promise:

優(yōu)秀實(shí)踐:

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

現(xiàn)在,在文件讀取過(guò)程中,事件循環(huán)仍然會(huì)繼續(xù)執(zhí)行。對(duì)于復(fù)雜的流程,async/await 可以簡(jiǎn)化異步邏輯。要避免使用同步操作以防止阻塞。

8. 阻塞事件循環(huán)

JavaScript 使用單線程的事件循環(huán)。阻塞它會(huì)導(dǎo)致整個(gè)程序暫停執(zhí)行。常見(jiàn)的阻塞因素包括:

  • 大量的計(jì)算任務(wù)
  • 同步 I/O
  • 未優(yōu)化的算法

例如:

function countPrimes(max) {
  // 未優(yōu)化的循環(huán)
  for (let i = 0; i <= max; i++) {
    // ...檢查是否為質(zhì)數(shù)...
  }
}

countPrimes(1000000); // 長(zhǎng)時(shí)間運(yùn)行!

這樣的代碼會(huì)同步執(zhí)行,從而阻塞其他事件。為了避免這種情況,你可以:

  • 延遲不必要的任務(wù)
  • 批量處理數(shù)據(jù)
  • 使用 Worker 線程
  • 尋找代碼優(yōu)化的機(jī)會(huì)

要確保事件循環(huán)可以流暢地運(yùn)行。定期進(jìn)行性能分析以捕獲阻塞性代碼。

9. 低效的錯(cuò)誤處理

在 JavaScript 中,正確地處理錯(cuò)誤是至關(guān)重要的。但要小心性能陷阱!

不佳實(shí)踐:

try {
  // ...
} catch (err) {
  console.error(err); // 僅僅是記錄
}

這樣雖然捕獲了錯(cuò)誤,但并未采取糾正措施。未處理的錯(cuò)誤通常會(huì)導(dǎo)致內(nèi)存泄漏或數(shù)據(jù)損壞。

更佳實(shí)踐:

try {
  // ...
} catch (err) {
  console.error(err);
  
  // 觸發(fā)錯(cuò)誤事件
  emitError(err); 
  
  // 將變量置為空
  obj = null;
  
  // 通知用戶
  showErrorNotice();
}

單純記錄錯(cuò)誤是不夠的!要清理殘留數(shù)據(jù),通知用戶,并考慮恢復(fù)選項(xiàng)。使用像 Sentry 這樣的工具來(lái)監(jiān)控生產(chǎn)環(huán)境中的錯(cuò)誤,并明確處理所有錯(cuò)誤。

11. 內(nèi)存泄漏

內(nèi)存泄漏是當(dāng)內(nèi)存被分配但從未被釋放的情況。隨著時(shí)間的推移,泄漏會(huì)累積并降低性能。

在 JavaScript 中,常見(jiàn)的內(nèi)存泄漏來(lái)源包括:

  • 未清理的事件監(jiān)聽(tīng)器
  • 過(guò)時(shí)的對(duì)已刪除 DOM 節(jié)點(diǎn)的引用
  • 不再需要的緩存數(shù)據(jù)
  • 在閉包中累積的狀態(tài)

例如:

function processData() {
  const data = [];

  // 使用閉包累積數(shù)據(jù)
  return function() {
    data.push(getData()); 
  }
}

const processor = processData();

// 長(zhǎng)時(shí)間運(yùn)行...持續(xù)持有對(duì)不斷增長(zhǎng)的數(shù)據(jù)數(shù)組的引用!

這個(gè)數(shù)組持續(xù)變大,但從未被清理。要修復(fù)這個(gè)問(wèn)題,你可以:

  • 使用弱引用
  • 清理事件監(jiān)聽(tīng)器
  • 刪除不再需要的引用
  • 限制閉包狀態(tài)的大小

持續(xù)監(jiān)控內(nèi)存使用情況,并關(guān)注其增長(zhǎng)趨勢(shì)。在問(wèn)題積累之前,主動(dòng)消除內(nèi)存泄漏。

12. 過(guò)度依賴外部庫(kù)

NPM (Node Package Manager) 提供了大量的庫(kù)和工具,讓開發(fā)者可以選擇和使用,但應(yīng)避免不加考慮地導(dǎo)入過(guò)多的依賴!每增加一個(gè)依賴,都會(huì)增加包的大小和潛在的攻擊面。

不佳做法:

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

僅為了一些小功能就導(dǎo)入整個(gè)庫(kù)。更好的做法是按需選擇性地導(dǎo)入所需的函數(shù):

良好做法:

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

只導(dǎo)入你真正需要用到的功能。定期審查依賴,剔除不再使用的庫(kù)。保持項(xiàng)目依賴精簡(jiǎn),盡量減少不必要的庫(kù)和工具。

13. 沒(méi)有充分利用緩存

緩存能夠通過(guò)重用之前的結(jié)果,以避免重復(fù)進(jìn)行耗時(shí)的計(jì)算,但人們常常忽視這一點(diǎn)。

不佳做法:

function generateReport() {
  // 執(zhí)行耗時(shí)的處理過(guò)程
  // 以生成報(bào)告數(shù)據(jù)...
}

generateReport(); // 計(jì)算一次
generateReport(); // 再次計(jì)算!

由于輸入沒(méi)有改變,報(bào)告完全可以被緩存:

良好做法:

let cachedReport;

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

  cachedReport = // 耗時(shí)的處理...
  return cachedReport; 
}

現(xiàn)在,重復(fù)的函數(shù)調(diào)用會(huì)很快。其他的緩存策略:

  • 像 Redis 這樣的內(nèi)存緩存
  • HTTP 緩存頭
  • 用于客戶端緩存的 LocalStorage
  • 用于資產(chǎn)緩存的 CDN

對(duì)適合緩存的數(shù)據(jù)進(jìn)行緩存,通常會(huì)顯著提升速度!

14. 未優(yōu)化的數(shù)據(jù)庫(kù)查詢

在與數(shù)據(jù)庫(kù)交互時(shí),低效的查詢會(huì)拖慢性能。應(yīng)避免的問(wèn)題有:

不佳做法:

// 沒(méi)有使用索引
db.find({name: 'John', age: 35}); 

// 查詢不必要的字段
db.find({first: 'John', last:'Doe', email:'john@doe.com'}, {first: 1, last: 1});

// 過(guò)多的獨(dú)立查詢
for (let id of ids) {
  const user = db.find({id});
}

這樣做沒(méi)有利用到索引、檢索了不需要的字段,還進(jìn)行了大量不必要的查詢。

良好做法:

// 在 'name' 上使用索引
db.find({name: 'John'}).hint({name: 1});

// 只獲取 'email' 字段
db.find({first: 'John'}, {email: 1}); 

// 一次查詢獲取多個(gè)用戶
const users = db.find({
  id: {$in: ids} 
});

分析并解釋查詢計(jì)劃,有針對(duì)性地創(chuàng)建索引,避免分散的多次查詢,優(yōu)化與數(shù)據(jù)存儲(chǔ)的交互。

15. 不恰當(dāng)?shù)?Promise 錯(cuò)誤處理

Promises 簡(jiǎn)化了異步代碼,但如果拒絕沒(méi)有得到處理,就會(huì)靜默地失敗。

不佳的做法:

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

getUser();

如果 fetch 拒絕,異常將不會(huì)被注意到。

良好的做法:

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

getUser();

通過(guò)鏈接 .catch() 來(lái)恰當(dāng)?shù)靥幚礤e(cuò)誤。其他建議:

  • 避免 Promise 嵌套地獄
  • 在最頂層處理拒絕
  • 配置未處理拒絕的跟蹤

不要忽視 Promise 的錯(cuò)誤!

16. 同步的網(wǎng)絡(luò)操作

網(wǎng)絡(luò)請(qǐng)求應(yīng)當(dāng)是異步的。但有時(shí)會(huì)使用同步版本:

不佳的做法:

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

這將在請(qǐng)求期間暫停事件循環(huán)。應(yīng)使用回調(diào)函數(shù):

良好的做法:

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

或者使用 Promises:

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

異步的網(wǎng)絡(luò)請(qǐng)求允許在等待響應(yīng)時(shí)進(jìn)行其他處理。避免使用同步網(wǎng)絡(luò)調(diào)用。

17. 文件 I/O 操作的低效性

同步地讀取/寫入文件會(huì)造成阻塞。例如:

糟糕的做法:

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

這會(huì)在磁盤 I/O 期間暫停程序執(zhí)行。更好的方式是:

良好的做法:

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

// 或者使用 Promise

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

這樣做使得在讀取文件期間,事件循環(huán)能夠繼續(xù)運(yùn)行。

對(duì)于多個(gè)文件,應(yīng)使用流:

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

避免使用同步文件操作。應(yīng)優(yōu)先使用回調(diào)、Promise 和流。

18. 忽略性能分析和優(yōu)化

性能問(wèn)題往往在明顯出現(xiàn)之前容易被忽視。然而,優(yōu)化應(yīng)該是一個(gè)持續(xù)的過(guò)程!首先,應(yīng)使用性能分析工具進(jìn)行測(cè)量:

  • 瀏覽器開發(fā)者工具時(shí)間線
  • Node.js 分析器
  • 第三方性能分析工具

即便性能看似正常,這些工具也能揭示一些優(yōu)化的機(jī)會(huì):

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

processOrders(allOrders);

分析器顯示 processOrders 函數(shù)耗時(shí) 200ms。經(jīng)過(guò)調(diào)查,我們發(fā)現(xiàn):

  • 循環(huán)沒(méi)有優(yōu)化
  • 內(nèi)部操作耗時(shí)高
  • 存在不必要的工作

我們逐步進(jìn)行優(yōu)化,最終版本僅需 5ms!

性能分析是優(yōu)化的指導(dǎo)方針。應(yīng)設(shè)立性能閾值,并在超過(guò)閾值時(shí)觸發(fā)告警。應(yīng)經(jīng)常進(jìn)行性能測(cè)試,并謹(jǐn)慎地進(jìn)行優(yōu)化。

19. 不必要的代碼重復(fù)

代碼重復(fù)不僅影響維護(hù)性,還降低了優(yōu)化空間??紤]以下例子:

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

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

  // ...邏輯... 
}

這里的信息提取邏輯是重復(fù)的。我們進(jìn)行重構(gòu):

function getCustomerInfo(data) {
  return {
    name: data.name, 
    email: data.email
  };
}

function userStats(user) {
  const { name, email } = getCustomerInfo(user);
  
  // ...邏輯...
}

function orderStats(order) {
  const { name, email } = getCustomerInfo(order);

  // ...邏輯...
}

現(xiàn)在,相同的邏輯只定義了一次。其他可行的修復(fù)措施包括:

  • 提取實(shí)用函數(shù)
  • 創(chuàng)建輔助類
  • 利用模塊實(shí)現(xiàn)重用性

盡量避免代碼重復(fù),這樣既能提高代碼質(zhì)量,也能提供更多優(yōu)化的機(jī)會(huì)。

結(jié)論

優(yōu)化 JavaScript 應(yīng)用性能是一個(gè)持續(xù)迭代的任務(wù)。通過(guò)掌握高效的編程方法和不斷地進(jìn)行性能評(píng)估,能夠明顯提升網(wǎng)站的運(yùn)行速度。

特別需要關(guān)注的幾個(gè)核心方面包括:降低 DOM 的修改頻率、運(yùn)用異步技術(shù)、避免阻塞性操作、精簡(jiǎn)依賴、利用數(shù)據(jù)緩存,以及消除冗余代碼。

隨著專注度和實(shí)踐經(jīng)驗(yàn)的不斷積累,你將能有效地定位到性能瓶頸,并針對(duì)特定業(yè)務(wù)場(chǎng)景進(jìn)行有針對(duì)性的優(yōu)化。這樣一來(lái),你將構(gòu)建出更快、更簡(jiǎn)潔和響應(yīng)更敏捷的 Web 應(yīng)用,從而贏得用戶的青睞。

總之,在性能優(yōu)化的路上,不能有絲毫的大意。遵循這些優(yōu)化建議,你會(huì)發(fā)現(xiàn)你的 JavaScript 代碼執(zhí)行速度得到了顯著提升。

原文標(biāo)題:Is Your Code Slow?: Avoid These 19 Common JavaScript and Node.js Mistakes,作者:JSDevJournal

責(zé)任編輯:華軒 來(lái)源: 51CTO
相關(guān)推薦

2019-07-09 14:50:15

Node.js前端工具

2020-10-29 09:00:00

Vue.jsjQuery前端

2017-04-10 13:28:32

Node.jsJavaScript

2010-06-29 10:39:48

SQL Server查

2022-10-28 15:51:24

JavaScript開發(fā)Node.js

2011-03-08 13:52:25

Proftpd

2011-12-23 13:58:57

node.js

2012-02-02 15:14:29

Node.js

2019-06-21 10:13:26

JavaScript錯(cuò)誤開發(fā)

2011-04-02 16:45:58

SQL Server查詢優(yōu)化

2010-05-21 18:01:13

2011-04-02 16:39:53

SQL Server查詢

2018-11-01 10:04:49

WiFi網(wǎng)速無(wú)線

2022-04-02 06:04:03

Node.js代碼緩存V8

2013-11-01 09:34:56

Node.js技術(shù)

2015-03-10 10:59:18

Node.js開發(fā)指南基礎(chǔ)介紹

2020-05-21 18:38:49

JavaScript前端技術(shù)

2019-11-04 05:37:52

SD-WAN軟件定義的廣域網(wǎng)網(wǎng)絡(luò)

2013-08-15 09:47:07

云遷移云技術(shù)

2016-08-02 10:22:10

JS微軟Node
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)