اندازه‌گیری و بهینه‌سازی کارایی کد جاوااسکریپت

تا این‌جا در این فصل با نحوه‌ی مدیریت حافظه در جاوااسکریپت آشنا شدید. اما کارایی یک برنامه فقط به مدیریت حافظه محدود نمی‌شود؛ سرعت اجرای کد نیز اهمیت زیادی دارد. پیش از هرگونه تلاش برای بهینه‌سازی، باید بتوان دقیقاً اندازه‌گیری کرد که کدام بخش از کد واقعاً کند است؛ چرا که حدس زدن بدون اندازه‌گیری، اغلب به بهینه‌سازی بخش‌های اشتباه منجر می‌شود.

اندازه‌گیری زمان اجرا با console.time

ساده‌ترین ابزار برای اندازه‌گیری سریع زمان اجرای یک بخش از کد، جفت متد console.time و console.timeEnd است:

اندازه‌گیری زمان اجرا با console.time
خروجی کنسول اینجا نمایش داده می‌شود...

هر دو متد باید با یک برچسب یکسان فراخوانی شوند تا مرورگر بداند کدام time با کدام timeEnd مرتبط است؛ این امکان می‌دهد چند اندازه‌گیری مستقل و هم‌زمان در کد وجود داشته باشد.

اندازه‌گیری دقیق‌تر با performance.now

برای اندازه‌گیری‌های دقیق‌تر و قابل‌استفاده در محاسبات برنامه (نه فقط چاپ در کنسول)، متد performance.now() مناسب‌تر است؛ این متد یک عدد اعشاری بر حسب میلی‌ثانیه، با دقتی بسیار بالاتر از Date.now()، برمی‌گرداند:

function measureExecutionTime(fn) {
  const start = performance.now();
  fn();
  const end = performance.now();
  return end - start;
}

const duration = measureExecutionTime(() => {
  let sum = 0;
  for (let i = 0; i < 10000000; i++) {
    sum += i;
  }
});

console.log(`زمان اجرا: ${duration.toFixed(2)} میلی‌ثانیه`);

تفاوت اصلی performance.now() با Date.now() این است که مقدار اولی به ساعت سیستم وابسته نیست و حتی اگر ساعت سیستم در حین اجرای برنامه تغییر کند، اندازه‌گیری زمان همچنان دقیق باقی می‌ماند.

الگوی رایج بهینه‌سازی: کش کردن نتایج تکراری

یکی از رایج‌ترین تکنیک‌های بهینه‌سازی، ذخیره‌ی نتیجه‌ی یک محاسبه‌ی سنگین برای استفاده‌ی مجدد، به‌جای محاسبه‌ی دوباره‌ی آن؛ تکنیکی که به آن Memoization گفته می‌شود:

function memoize(fn) {
  const cache = new Map();

  return function (...args) {
    const key = JSON.stringify(args);

    if (cache.has(key)) {
      return cache.get(key);
    }

    const result = fn(...args);
    cache.set(key, result);
    return result;
  };
}

function slowSquare(n) {
  for (let i = 0; i < 100000000; i++) {} // شبیه‌سازی محاسبه‌ی سنگین
  return n * n;
}

const fastSquare = memoize(slowSquare);

console.log(fastSquare(5)); // محاسبه‌ی کامل و کند
console.log(fastSquare(5)); // بازیابی فوری از cache، بدون محاسبه‌ی مجدد

در این الگو، از Map (که در فصل ۲۴ بررسی شد) برای نگهداری نتایج قبلی استفاده شده است؛ فراخوانی دوم با همان آرگومان‌ها، به‌جای اجرای دوباره‌ی منطق سنگین slowSquare، نتیجه را مستقیماً از حافظه بازمی‌گرداند.

محدود کردن فراخوانی توابع پرتکرار: Debounce و Throttle

رویدادهایی مثل scroll، resize یا input می‌توانند در کسری از ثانیه، ده‌ها بار فعال شوند. اجرای بی‌قید‌وشرط یک تابع سنگین به ازای هر بار فعال‌شدن این رویدادها، می‌تواند عملکرد صفحه را به‌شدت کاهش دهد. دو تکنیک رایج برای کنترل این وضعیت، Debounce و Throttle هستند:

تکنیک رفتار کاربرد رایج
Debounce تابع فقط پس از توقف رویداد برای مدتی مشخص اجرا می‌شود جست‌وجوی زنده هنگام تایپ
Throttle تابع حداکثر یک‌بار در هر بازه‌ی زمانی مشخص اجرا می‌شود رویداد scroll برای بارگذاری محتوای بیشتر
function debounce(fn, delay) {
  let timeoutId;
  return function (...args) {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => fn(...args), delay);
  };
}

const search = document.querySelector("#search");
const handleSearch = debounce((event) => {
  console.log("در حال جست‌وجو برای:", event.target.value);
}, 300);

search.addEventListener("input", handleSearch);

در این الگو، تابع debounce از تایمرها (که در فصل ۱۷ بررسی شد) برای لغو مداوم اجرای قبلی و فقط اجرای آخرین درخواست، پس از توقف تایپ کاربر برای ۳۰۰ میلی‌ثانیه، استفاده می‌کند.

نکات کلیدی

  • پیش از بهینه‌سازی، باید با ابزارهایی مثل console.time یا performance.now، بخش واقعاً کند کد شناسایی شود.
  • performance.now دقت بالاتری نسبت به Date.now دارد و برای اندازه‌گیری‌های داخل کد مناسب‌تر است.
  • Memoization با کش کردن نتایج محاسبات سنگین، از اجرای مجدد و غیرضروری آن‌ها جلوگیری می‌کند.
  • Debounce و Throttle، دو تکنیک اصلی برای کنترل فراخوانی توابع در رویدادهای پرتکرار مثل scroll و input هستند.

جمع‌بندی

بهینه‌سازی مؤثر همیشه با اندازه‌گیری دقیق شروع می‌شود، نه با حدس زدن بخش‌های احتمالاً کند. ابزارهایی مثل console.time و performance.now این اندازه‌گیری را ممکن می‌کنند، و تکنیک‌هایی مثل Memoization، Debounce و Throttle، راه‌حل‌های عملی برای مشکلات رایج کارایی در برنامه‌های واقعی هستند. با تکمیل این درس، فصل مدیریت حافظه و کارایی به پایان می‌رسد.