تحسين أداء JavaScript
تحسين أداء JavaScript
يؤثر أداء JavaScript بشكل مباشر على تجربة المستخدم وأوقات التحميل والتفاعلية. غالبًا ما ترسل تطبيقات الويب الحديثة حزم JavaScript كبيرة يمكن أن تبطئ تحميل الصفحة والتنفيذ. يغطي هذا الدرس تقنيات تحسين أداء JavaScript من خلال تحسين الحزم وتقسيم الكود وأنماط التنفيذ الفعالة.
تحسين حجم الحزمة
يؤدي تقليل حجم حزمة JavaScript إلى تحسين أوقات التنزيل وأداء التحليل:
<script src="/js/app.bundle.js"></script> <!-- 2.5 ميجابايت -->
<!-- بعد: أجزاء محسّنة -->
<script src="/js/vendor.js"></script> <!-- 500 كيلوبايت -->
<script src="/js/app.js"></script> <!-- 300 كيلوبايت -->
<script src="/js/lazy-features.js" defer></script> <!-- 200 كيلوبايت -->
Tree Shaking (إزالة الكود الميت)
تزيل تقنية Tree Shaking الكود غير المستخدم من حزمك. تعمل مع وحدات ES6:
import _ from 'lodash'; // 70 كيلوبايت
const result = _.debounce(fn, 300);
// جيد: استيراد ما تحتاجه فقط
import debounce from 'lodash/debounce'; // 2 كيلوبايت
const result = debounce(fn, 300);
// الأفضل: استخدام استيراد وحدات ES6
import { debounce } from 'lodash-es'; // قابل لإزالة الكود الميت
// إعداد Webpack لإزالة الكود الميت
module.exports = {
mode: 'production',
optimization: {
usedExports: true,
minimize: true
}
};
تقسيم الكود
قسّم الكود إلى أجزاء أصغر يتم تحميلها عند الطلب:
import React, { lazy, Suspense } from 'react';
const Dashboard = lazy(() => import('./Dashboard'));
const Profile = lazy(() => import('./Profile'));
const Settings = lazy(() => import('./Settings'));
function App() {
return (
<Suspense fallback={<div>جاري التحميل...</div>}>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/profile" element={<Profile />} />
<Route path="/settings" element={<Settings />} />
</Routes>
</Suspense>
);
}
// الاستيراد الديناميكي (JavaScript عادي)
document.querySelector('#btn').addEventListener('click', async () => {
const module = await import('./heavy-feature.js');
module.initialize();
});
التحميل الكسول
أجّل تحميل JavaScript غير الحرج حتى الحاجة إليه:
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
import('./chart-library.js').then(module => {
module.renderChart(entry.target);
});
observer.unobserve(entry.target);
}
});
});
document.querySelectorAll('.chart-container').forEach(el => {
observer.observe(el);
});
// التحميل الكسول للسكريبتات مع السمات
const script = document.createElement('script');
script.src = 'analytics.js';
script.async = true;
script.defer = true;
document.body.appendChild(script);
Web Workers (خيوط العمل الخلفية)
انقل العمليات الحسابية الثقيلة إلى خيوط الخلفية:
self.addEventListener('message', (e) => {
const data = e.data;
// عمليات حسابية ثقيلة
const result = processLargeDataset(data);
self.postMessage(result);
});
function processLargeDataset(data) {
// حسابات معقدة قد تحجب واجهة المستخدم
return data.map(item => expensiveOperation(item));
}
// main.js
const worker = new Worker('worker.js');
worker.postMessage(largeDataset);
worker.addEventListener('message', (e) => {
const result = e.data;
updateUI(result);
});
// التنظيف عند الانتهاء
worker.terminate();
requestAnimationFrame
حسّن الرسوم المتحركة والتحديثات المرئية:
function moveElement() {
element.style.left = position + 'px';
position += 5;
setTimeout(moveElement, 16); // ~60fps لكنه غير دقيق
}
// جيد: استخدام requestAnimationFrame
function moveElement() {
element.style.left = position + 'px';
position += 5;
if (position < targetPosition) {
requestAnimationFrame(moveElement);
}
}
requestAnimationFrame(moveElement);
// تجميع قراءات وكتابات DOM
function optimizedLayout() {
// تجميع القراءات
const height1 = el1.offsetHeight;
const height2 = el2.offsetHeight;
// تجميع الكتابات
requestAnimationFrame(() => {
el1.style.height = height2 + 'px';
el2.style.height = height1 + 'px';
});
}
منع تسريبات الذاكرة
حدد ومنع أنماط تسريب الذاكرة الشائعة:
class Component {
constructor() {
this.handleClick = () => console.log('clicked');
document.addEventListener('click', this.handleClick);
}
destroy() {
// نسيت إزالة المستمع - تسريب!
}
}
// مصلح: تنظيف المستمعين
class Component {
constructor() {
this.handleClick = () => console.log('clicked');
document.addEventListener('click', this.handleClick);
}
destroy() {
document.removeEventListener('click', this.handleClick);
}
}
// تسريب: المؤقتات لم يتم مسحها
const intervalId = setInterval(() => {
updateData();
}, 1000);
// المكون يُزال لكن الفاصل الزمني يستمر - تسريب!
// مصلح: مسح المؤقتات
const intervalId = setInterval(() => updateData(), 1000);
window.addEventListener('beforeunload', () => {
clearInterval(intervalId);
});
// تسريب: عناصر DOM منفصلة
let cache = [];
function addElement() {
const div = document.createElement('div');
cache.push(div); // يحتفظ بالمرجع حتى بعد الإزالة
document.body.appendChild(div);
}
// مصلح: مسح المراجع
function addElement() {
const div = document.createElement('div');
document.body.appendChild(div);
// لا تخزن مراجع لعناصر DOM
}
مراقبة الأداء
const startTime = performance.now();
heavyFunction();
const endTime = performance.now();
console.log(`استغرق التنفيذ ${endTime - startTime}ms`);
// مراقبة المهام الطويلة (أكثر من 50 ملي ثانية)
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.warn('تم اكتشاف مهمة طويلة:', {
duration: entry.duration,
startTime: entry.startTime
});
}
});
observer.observe({ entryTypes: ['longtask'] });
// User Timing API
performance.mark('feature-start');
await loadFeature();
performance.mark('feature-end');
performance.measure('feature', 'feature-start', 'feature-end');
const measure = performance.getEntriesByName('feature')[0];
console.log(`تحميل الميزة: ${measure.duration}ms`);