>  기사  >  웹 프론트엔드  >  코드가 느리게 실행되나요? 프로그램을 빠른 속도로 계속 실행하려면 다음 19가지 일반적인 JavaScript 및 Node.js 실수를 피하세요.

코드가 느리게 실행되나요? 프로그램을 빠른 속도로 계속 실행하려면 다음 19가지 일반적인 JavaScript 및 Node.js 실수를 피하세요.

DDD
DDD원래의
2023-09-22 10:50:481478검색

느리거나 느린 웹사이트는 아마추어의 징조인 반면, 원활하고 최적화된 경험은 사용자를 기쁘게 하고 전문가를 돋보이게 합니다.

그러나 진정한 고성능 웹 애플리케이션을 만드는 데에는 함정이 가득합니다. 버그가 많아 사용자가 깨닫지도 못하는 사이에 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'!

색상은 전역적이므로 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(&#39;li&#39;);
  li.textContent = i;
  
  ul.appendChild(li);
}

이렇게 하면 목록 항목이 하나씩 추가됩니다. 먼저 문자열을 작성한 다음 .innerHTML을 설정하는 것이 좋습니다.

const ul = document.getElementById(&#39;list&#39;);
let html = &#39;&#39;;
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(&#39;beforeend&#39;, msg);

이 내용은 모든 메시지에 순수하게 삽입됩니다. 업데이트를 제한하는 것이 더 좋습니다:

올바른 예:

let chatLogHTML = &#39;&#39;;
const throttleTime = 100; // ms
// New message received  
chatLogHTML += `<div>${messageText}</div>`;
// Throttle DOM updates
setTimeout(() => {
  chatLog.innerHTML = chatLogHTML;
  chatLogHTML = &#39;&#39;; 
}, throttleTime);

이제 우리는 최대 100밀리초마다 업데이트하므로 DOM 작업을 낮게 유지합니다.

매우 동적인 UI의 경우 React와 같은 가상 DOM 라이브러리를 고려해보세요. 이는 가상 표현을 사용하여 DOM 조작을 최소화합니다.

4. 활동 위임 부족

이벤트 리스너를 여러 요소에 연결하면 불필요한 오버헤드가 발생합니다. 각 행에 삭제 버튼이 있는 테이블을 생각해 보세요.

부정적 예:

const rows = document.querySelectorAll(&#39;table tr&#39;);
rows.forEach(row => {
  const deleteBtn = row.querySelector(&#39;.delete&#39;);  
  deleteBtn.addEventListener(&#39;click&#39;, handleDelete);
});

이렇게 하면 각 삭제 버튼에 대한 리스너가 추가됩니다. 이벤트 위임을 더 잘 사용해야 함:

올바른 예:

const table = document.querySelector(&#39;table&#39;);
table.addEventListener(&#39;click&#39;, e => {
  if (e.target.classList.contains(&#39;delete&#39;)) {
    handleDelete(e);
  }
});

이제 .net에는 리스너가 하나만 있고 메모리 오버헤드가 적습니다.

이벤트 위임은 이벤트 버블링을 활용합니다. 리스너는 여러 하위 항목의 이벤트를 처리할 수 있습니다. 해당되는 경우 위임을 사용하십시오.

5. 비효율적인 문자열 연결

루프에서 문자열을 연결하면 성능이 영향을 받습니다. 다음 코드를 고려해보세요.

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

새 문자열을 만들려면 메모리 할당이 필요합니다. 배열을 사용하는 것이 더 좋습니다:

const parts = [];
for (let i = 0; i < 10; i++) {
  parts.push(&#39;<div>&#39;, i, &#39;</div>&#39;);
}
const html = parts.join(&#39;&#39;);

배열을 구축하면 중간 문자열의 필요성이 최소화됩니다. .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(&#39;file.json&#39;); // blocks!

这会在从磁盘读取时停止执行。相反,如果使用回调或承诺:

正确示例:

fs.readFile(&#39;file.json&#39;, (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 &#39;lodash&#39;;
import moment from &#39;moment&#39;; 
import validator from &#39;validator&#39;;
// etc...

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

正确示例:

import cloneDeep from &#39;lodash/cloneDeep&#39;;
import { format } from &#39;date-fns&#39;;
import { isEmail } from &#39;validator&#39;;

只导入您需要的内容。定期检查依赖关系以删除未使用的依赖关系。保持捆绑精简并最大限度地减少依赖性。

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: &#39;John&#39;, age: 35}); 
// Unecessary fields
db.find({first: &#39;John&#39;, last:&#39;Doe&#39;, email:&#39;john@doe.com&#39;}, {first: 1, last: 1});
// Too many separate queries
for (let id of ids) {
  const user = db.find({id});
}

这无法利用索引、检索未使用的字段并执行过多的查询。

正确示例:

// Use index on &#39;name&#39; 
db.find({name: &#39;John&#39;}).hint({name: 1});
// Only get &#39;email&#39; field
db.find({first: &#39;John&#39;}, {email: 1}); 
// Get users in one query
const users = db.find({
  id: {$in: ids} 
});

分析并解释计划。战略性地创建索引。避免多次零散的查询。优化数据存储交互。

14. Promise 中错误处理不当

Promise 简化了异步代码。但未经处理的拒绝就是无声的失败!

反面例子:

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

如果fetch拒绝,异常就不会被注意到。

正确示例:

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

链接.catch()可以正确处理错误。

15. 同步网络操作

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

反面例子:

const data = http.getSync(&#39;http://example.com/data&#39;); // blocks!

这会在请求期间停止事件循环。相反,使用回调:

正确示例:

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

或者:

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

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

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

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

反面例子:

const contents = fs.readFileSync(&#39;file.txt&#39;); // blocks!

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

正确示例:

fs.readFile(&#39;file.txt&#39;, (err, contents) => {
  // ...
});
// or promises
fs.promises.readFile(&#39;file.txt&#39;)
   .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 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.