کارایی و بهینه‌سازی

کارایی جاوا اسکریپت، مدیریت حافظه و Memory Leak، بهینه‌سازی DOM و Event با Debouncing و Throttling، Lazy Loading، Code Splitting، Performance API و Core Web Vitals.

چرا کارایی مهم است؟

کاربران از صفحه‌ای که بیش از ۳ ثانیه طول بکشد ناراضی‌اند — سرعت = تجربه + سئو + نرخ تبدیل:

اهداف:
- First Paint < 1s
- Interactive < 3s  
- 60fps انیمیشن (16ms per frame)
- بدون jank (گرفتگی)

اندازه‌گیری — قبل از بهینه‌سازی

// console.time:
console.time("عملیات");
heavyOperation();
console.timeEnd("عملیات"); // عملیات: 152ms

// performance.now — دقت میکروثانیه:
const start = performance.now();
heavyOperation();
const end = performance.now();
console.log(`زمان: ${(end - start).toFixed(2)}ms`);

// Performance API — زمان‌بندی صفحات:
const nav = performance.getEntriesByType("navigation")[0];
console.log("DOM آماده:", nav.domContentLoadedEventEnd);
console.log("بارگذاری کامل:", nav.loadEventEnd);

// Resource Timing — هر فایل:
performance.getEntriesByType("resource").forEach(r => {
    console.log(r.name, r.duration.toFixed(0) + "ms");
});

مدیریت حافظه

// جاوا اسکریپت GC خودکار دارد — اما کمکش کنید:

// نشتی ۱ — متغیرهای فراموش‌شده:
function process() {
    const bigData = new Array(1000000).fill("داده");
    // bigData بعد از return زنده می‌ماند اگر بسته شود:
    return function() {
        return bigData.length;  // نگه‌داشتن!
    };
}

// نشتی ۲ — تایمرها:
const timer = setInterval(() => {...}, 1000);
// فراموش کردن clearInterval → نشتی!

// نشتی ۳ — شنونده‌های رویداد:
element.addEventListener("click", handler);
// حذف عنصر بدون removeEventListener → نشتی!

// پاک‌سازی درست:
function createWidget(element) {
    const handler = () => console.log("کلیک");
    element.addEventListener("click", handler);

    // تابع پاک‌سازی برگردان:
    return () => {
        element.removeEventListener("click", handler);
    };
}

const cleanup = createWidget(el);
// بعد از استفاده:
cleanup();  // بدون نشتی

بهینه‌سازی DOM

// بد — دستکاری مکرر: ❌
for (let i = 0; i < 1000; i++) {
    document.body.innerHTML += `<div>${i}</div>`;  // ۱۰۰۰ reflow!
}

// خوب — یک‌بار: ✓
const fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
    const div = document.createElement("div");
    div.textContent = i;
    fragment.appendChild(div);
}
document.body.appendChild(fragment);  // یک reflow!

// خواندن و نوشتن جداگانه:
// بد — خواندن/نوشتن متناوب:
el.style.width = "100px";
const h = el.offsetHeight;   // reflow اجباری!
el.style.height = h + "px";

// خوب — همه خواندن‌ها اول:
const height = el.offsetHeight;   // یک reflow
el.style.width = "100px";
el.style.height = height + "px";

Debouncing — لرزش‌گیری

صبر تا توقف کاربر — برای جستجو، resize:

function debounce(fn, delay = 300) {
    let timer;
    return function (...args) {
        clearTimeout(timer);
        timer = setTimeout(() => fn.apply(this, args), delay);
    };
}

// استفاده — جستجو فقط بعد از توقف تایپ:
searchInput.addEventListener("input", debounce((e) => {
    console.log("جستجو برای:", e.target.value);
}, 500));

// resize:
window.addEventListener("resize", debounce(() => {
    console.log("اندازه جدید:", window.innerWidth);
}, 250));

Throttling — محدودسازی نرخ

حداکثر یک‌بار در بازه — برای scroll، mousemove:

function throttle(fn, limit = 200) {
    let inThrottle = false;
    return function (...args) {
        if (!inThrottle) {
            fn.apply(this, args);
            inThrottle = true;
            setTimeout(() => inThrottle = false, limit);
        }
    };
}

// اسکرول — حداکثر ۵ بار در ثانیه:
window.addEventListener("scroll", throttle(() => {
    console.log("اسکرول:", window.scrollY);
}, 200));

// mousemove:
canvas.addEventListener("mousemove", throttle(draw, 16));  // ~60fps

Lazy Loading — بارگذاری تنبل

// تصاویر — native:
// <img src="placeholder.jpg" data-src="real.jpg" loading="lazy">

// با IntersectionObserver:
const imageObserver = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
        if (entry.isIntersecting) {
            const img = entry.target;
            img.src = img.dataset.src;
            imageObserver.unobserve(img);
        }
    });
});

document.querySelectorAll("img[data-src]").forEach(img => {
    imageObserver.observe(img);
});

// محتوا — بارگذاری بخش‌ها:
function lazySection(selector, loader) {
    const el = document.querySelector(selector);
    const observer = new IntersectionObserver(async (entries) => {
        if (entries[0].isIntersecting) {
            observer.disconnect();
            el.innerHTML = "در حال بارگذاری...";
            el.innerHTML = await loader();
        }
    }, { rootMargin: "200px" });  // قبل از رسیدن
    observer.observe(el);
}

Code Splitting — تقسیم کد

// به‌جای همه چیز در یک فایل:
// import { Chart } from "chart.js";  // ۲۰۰KB — همیشه؟

// فقط وقتی لازم است:
async function showChart() {
    const { Chart } = await import("chart.js");  // دانلود لحظه‌ای
    new Chart(ctx, data);
}

// مسیریابی — هر صفحه جدا:
const routes = {
    "/": () => import("./pages/home.js"),
    "/dashboard": () => import("./pages/dashboard.js"),
    "/settings": () => import("./pages/settings.js")
};

async function navigate(path) {
    const page = await routes[path]();
    document.querySelector("#app").innerHTML = page.default;
}

Rendering Performance

// انیمیشن فقط با transform و opacity:
// خوب — GPU: ✓
element.style.transform = "translateX(100px)";
element.style.opacity = "0.5";

// بد — CPU و reflow: ❌
element.style.left = "100px";     // layout
element.style.width = "200px";    // layout

// will-change — به مرورگر خبر بده:
element.style.willChange = "transform";

// requestAnimationFrame برای انیمیشن JS:
function animate() {
    element.style.transform = `translateX(${x++}px)`;
    requestAnimationFrame(animate);
}

// Web Workers برای محاسبه:
// سنگین → Worker → نتیجه → UI

PerformanceObserver — نظارت

// مشاهده متریک‌ها:
const observer = new PerformanceObserver((list) => {
    for (const entry of list.getEntries()) {
        console.log(entry.name, entry.duration);
    }
});

observer.observe({ entryTypes: ["measure", "navigation", "resource"] });

// اندازه‌گیری دستی:
performance.mark("start");
heavyOperation();
performance.mark("end");
performance.measure("عملیات سنگین", "start", "end");

const measures = performance.getEntriesByName("عملیات سنگین");
console.log(measures[0].duration);

Core Web Vitals

متریکمعنیهدف
LCPبزرگ‌ترین محتوای قابل دید< 2.5s
FIDاولین تعامل — تأخیر< 100ms
CLSجابجایی ناخواسته چیدمان< 0.1
// اندازه‌گیری LCP:
new PerformanceObserver((list) => {
    const entries = list.getEntries();
    const lastEntry = entries[entries.length - 1];
    console.log("LCP:", lastEntry.startTime);
}).observe({ type: "largest-contentful-paint", buffered: true });

// CLS:
let clsValue = 0;
new PerformanceObserver((list) => {
    for (const entry of list.getEntries()) {
        if (!entry.hadRecentInput) clsValue += entry.value;
    }
    console.log("CLS:", clsValue);
}).observe({ type: "layout-shift", buffered: true });

Lighthouse

ابزار گوگل برای ممیزی کارایی:

بهینه‌سازی‌های کلیدی

مشکلراه‌حل
JS سنگین اولیهCode Splitting + Tree Shaking
رویداد مکررDebounce/Throttle
تصاویر سنگینLazy Load + فرمت WebP
reflow زیادDocumentFragment + batch
محاسبه سنگینWeb Worker
نشتی حافظهcleanup function

این مبحث بخشی از دوره آموزش کامل جاوا اسکریپت است.

ابزارهای مرتبط