Web サイトが遅い、または遅れているのはアマチュアの兆候ですが、スムーズで最適化されたエクスペリエンスはユーザーを喜ばせ、プロとは一線を画します。
しかし、本当に高パフォーマンスの Web アプリケーションを作成するには、落とし穴がたくさんあります。バグはたくさんあり、気付かないうちに JavaScript の速度を低下させる可能性があります。小さな見落としがあるとコードが肥大化し、静かに少しずつ速度が低下する可能性があります。
これはどうなりましたか?
意図せずに JavaScript の速度を低下させる一般的な方法が多数あることが判明しました。時間が経つと、これにより Web サイトのパフォーマンスが妨げられる可能性があります。
これらの間違いは避けることができます。
今日は、JavaScript および Node.js アプリケーションの速度を静かに低下させる可能性がある 19 のパフォーマンスの落とし穴に焦点を当てます。コードを最適化するための具体的な例と実用的な解決策を使用して、これらの問題の原因を探ります。
ユーザーを満足させるスムーズな Web エクスペリエンスを作成するには、これらの危険を特定して排除することが重要です。それでは、詳しく見ていきましょう!
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 要素を更新するときは、一度に 1 つのノードを操作するのではなく、バッチ変更を行ってください。次の例を考えてみましょう:
const ul = document.getElementById('list'); for (let i = 0; i < 10; i++) { const li = document.createElement('li'); li.textContent = i; ul.appendChild(li); }
これにより、リスト項目が 1 つずつ追加されます。最初に文字列を構築してから、.innerHTML を設定することをお勧めします。
const ul = document.getElementById('list'); let html = ''; for (let i = 0; i < 10; i++) { html += `<li>${i}</li>`; } ul.innerHTML = html;
文字列を構築すると、リフローが最小限に抑えられます。 DOM を 10 回ではなく 1 回更新します。
複数の更新の場合は、変更をビルドして最後に適用します。さらに良いのは、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 にはリスナーが 1 つだけあり、メモリ オーバーヘッドが少なくなります。
イベント委任ではイベント バブリングを使用します。リスナーは複数の子孫からのイベントを処理できます。該当する場合は常に委任を使用します。
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 更改、利用异步技术、消除阻塞操作、减少依赖性、利用缓存以及删除不需要的重复。
以上がコードの実行が遅いですか?プログラムの高速実行を維持するには、JavaScript と Node.js でよくある 19 の間違いを回避してくださいの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。