أفضل ممارسات جافاسكريبت وتحسين الأداء
أفضل ممارسات جافاسكريبت وتحسين الأداء
مرحبًا بك في الدرس الأخير من دورة أساسيات JavaScript! في هذا الدرس الشامل، سنستكشف أفضل الممارسات وتقنيات تحسين الأداء واعتبارات الأمان واستراتيجيات كتابة كود JavaScript قابل للصيانة وفعال. ستساعدك هذه الممارسات على أن تصبح مطور JavaScript محترف.
1. تنظيم وهيكلة الكود
تنظيم الملفات
نظم كودك في وحدات وملفات منطقية:
<!-- سيء: كل شيء في ملف واحد -->
<script>
// 5000 سطر من الكود المختلط...
</script>
<!-- جيد: هيكل منظم -->
project/
├── src/
│ ├── components/
│ │ ├── header.js
│ │ └── footer.js
│ ├── utils/
│ │ ├── validation.js
│ │ └── formatting.js
│ ├── services/
│ │ └── api.js
│ └── app.js
└── index.html
نمط الوحدات (Module Pattern)
استخدم وحدات ES6 لتنظيم الكود:
// utils/validation.js
export function validateEmail(email) {
const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return re.test(email);
}
export function validatePhone(phone) {
const re = /^\+?[1-9]\d{1,14}$/;
return re.test(phone);
}
// app.js
import { validateEmail, validatePhone } from './utils/validation.js';
const email = 'user@example.com';
console.log(validateEmail(email)); // true
اصطلاحات التسمية
اتبع اصطلاحات تسمية متسقة:
// المتغيرات والدوال: camelCase
let userName = 'John';
function getUserData() { }
// الثوابت: UPPER_SNAKE_CASE
const MAX_RETRY_ATTEMPTS = 3;
const API_BASE_URL = 'https://api.example.com';
// الفئات: PascalCase
class UserProfile { }
class ShoppingCart { }
// الخصائص الخاصة: بادئة بشرطة سفلية
class BankAccount {
constructor() {
this._balance = 0; // اصطلاح خاص
}
}
// المتغيرات المنطقية: استخدم بادئة is/has/can
let isActive = true;
let hasPermission = false;
let canEdit = true;
2. أفضل ممارسات المتغيرات
فضّل const، استخدم let، تجنب var
استخدم دائمًا const بشكل افتراضي، واستخدم let فقط عند الحاجة لإعادة التعيين:
// سيء: استخدام var
var count = 0;
var name = 'John';
// جيد: استخدم const بشكل افتراضي
const name = 'John';
const MAX_ITEMS = 100;
const config = { theme: 'dark' };
// جيد: استخدم let عند الحاجة لإعادة التعيين
let count = 0;
count++;
let status = 'pending';
status = 'completed';
أسماء متغيرات ذات معنى
استخدم أسماء وصفية تكشف النية:
// سيء: أسماء غير واضحة
let d = new Date();
let x = users.filter(u => u.a);
let temp = calculate();
// جيد: أسماء وصفية
let currentDate = new Date();
let activeUsers = users.filter(user => user.isActive);
let totalPrice = calculateTotalPrice();
// سيء: حرف واحد (إلا في الحلقات)
let a = 5;
let b = 10;
// جيد: وصفي
let width = 5;
let height = 10;
// مقبول: متغيرات حلقة شائعة
for (let i = 0; i < items.length; i++) {
console.log(items[i]);
}
تجنب المتغيرات العامة
قلل من تلوث النطاق العام:
// سيء: متغيرات عامة
var userData = {};
var isLoggedIn = false;
function login() {
isLoggedIn = true;
}
// جيد: استخدم وحدات أو IIFE
const App = (function() {
let userData = {};
let isLoggedIn = false;
function login() {
isLoggedIn = true;
}
return {
login
};
})();
// أفضل: وحدات ES6
// auth.js
let isLoggedIn = false;
export function login() {
isLoggedIn = true;
}
export function isAuthenticated() {
return isLoggedIn;
}
3. أفضل ممارسات الدوال
مبدأ المسؤولية الواحدة
يجب أن تقوم كل دالة بشيء واحد بشكل جيد:
// سيء: دالة تفعل الكثير
function processUserData(user) {
// التحقق
if (!user.email) return false;
// التحويل
user.name = user.name.toUpperCase();
// الحفظ في قاعدة البيانات
database.save(user);
// إرسال البريد الإلكتروني
emailService.send(user.email);
// التسجيل
console.log('User processed');
}
// جيد: فصل الاهتمامات
function validateUser(user) {
return user.email !== undefined;
}
function normalizeUserData(user) {
return {
...user,
name: user.name.toUpperCase()
};
}
function saveUser(user) {
return database.save(user);
}
function sendWelcomeEmail(user) {
return emailService.send(user.email);
}
function processUser(user) {
if (!validateUser(user)) {
throw new Error('Invalid user data');
}
const normalized = normalizeUserData(user);
const saved = saveUser(normalized);
sendWelcomeEmail(saved);
console.log('User processed successfully');
return saved;
}
الدوال النقية (Pure Functions)
اكتب دوال نقية عندما يكون ذلك ممكنًا (بدون آثار جانبية، نفس المدخل = نفس المخرج):
// غير نقية: تعدل الحالة الخارجية
let total = 0;
function addToTotal(value) {
total += value; // تأثير جانبي
return total;
}
// نقية: بدون آثار جانبية
function add(a, b) {
return a + b;
}
// غير نقية: تعدل المدخل
function addItem(cart, item) {
cart.items.push(item); // تعديل المدخل
return cart;
}
// نقية: ترجع كائن جديد
function addItem(cart, item) {
return {
...cart,
items: [...cart.items, item]
};
}
حجم الدالة وتعقيدها
احتفظ بالدوال صغيرة ومركزة:
// سيء: دالة طويلة ومعقدة
function calculateOrderTotal(order) {
let subtotal = 0;
for (let item of order.items) {
subtotal += item.price * item.quantity;
}
let discount = 0;
if (order.couponCode) {
if (order.couponCode === 'SAVE10') {
discount = subtotal * 0.1;
} else if (order.couponCode === 'SAVE20') {
discount = subtotal * 0.2;
}
}
let tax = 0;
if (order.country === 'US') {
tax = (subtotal - discount) * 0.08;
} else if (order.country === 'UK') {
tax = (subtotal - discount) * 0.20;
}
return subtotal - discount + tax;
}
// جيد: مقسمة إلى دوال أصغر
function calculateSubtotal(items) {
return items.reduce((sum, item) =>
sum + (item.price * item.quantity), 0);
}
function calculateDiscount(subtotal, couponCode) {
const discounts = {
'SAVE10': 0.1,
'SAVE20': 0.2
};
return subtotal * (discounts[couponCode] || 0);
}
function calculateTax(amount, country) {
const taxRates = {
'US': 0.08,
'UK': 0.20
};
return amount * (taxRates[country] || 0);
}
function calculateOrderTotal(order) {
const subtotal = calculateSubtotal(order.items);
const discount = calculateDiscount(subtotal, order.couponCode);
const taxableAmount = subtotal - discount;
const tax = calculateTax(taxableAmount, order.country);
return taxableAmount + tax;
}
4. استراتيجية معالجة الأخطاء
معالجة أخطاء متسقة
استخدم كتل try-catch بشكل مناسب:
// سيء: فشل صامت
function loadUserData(userId) {
try {
const data = localStorage.getItem(userId);
return JSON.parse(data);
} catch (e) {
// فشل صامت - سيء!
}
}
// جيد: معالجة أخطاء صحيحة
function loadUserData(userId) {
try {
const data = localStorage.getItem(userId);
if (!data) {
throw new Error('User data not found');
}
return JSON.parse(data);
} catch (error) {
console.error('Failed to load user data:', error);
throw error; // إعادة الرمي أو المعالجة المناسبة
}
}
// معالجة الأخطاء غير المتزامنة
async function fetchUserProfile(userId) {
try {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error('Error fetching user profile:', error);
throw error;
}
}
فئات أخطاء مخصصة
أنشئ أخطاء مخصصة لمعالجة أفضل للأخطاء:
// تعريف فئات أخطاء مخصصة
class ValidationError extends Error {
constructor(message, field) {
super(message);
this.name = 'ValidationError';
this.field = field;
}
}
class NetworkError extends Error {
constructor(message, statusCode) {
super(message);
this.name = 'NetworkError';
this.statusCode = statusCode;
}
}
// استخدام الأخطاء المخصصة
function validateEmail(email) {
if (!email) {
throw new ValidationError('Email is required', 'email');
}
if (!email.includes('@')) {
throw new ValidationError('Invalid email format', 'email');
}
return true;
}
// معالجة أنواع أخطاء محددة
try {
validateEmail('');
} catch (error) {
if (error instanceof ValidationError) {
console.log(`Validation failed for ${error.field}: ${error.message}`);
} else {
console.error('Unexpected error:', error);
}
}
5. تحسين الأداء
أفضل ممارسات معالجة DOM
قلل من الوصول إلى DOM واجمع التحديثات:
// سيء: معالجات DOM متعددة
function addItems(items) {
const list = document.getElementById('list');
items.forEach(item => {
const li = document.createElement('li');
li.textContent = item;
list.appendChild(li); // يسبب reflow في كل مرة
});
}
// جيد: تحديثات DOM مجمعة
function addItems(items) {
const list = document.getElementById('list');
const fragment = document.createDocumentFragment();
items.forEach(item => {
const li = document.createElement('li');
li.textContent = item;
fragment.appendChild(li);
});
list.appendChild(fragment); // reflow واحد فقط
}
// أفضل: استخدم innerHTML للقوائم الكبيرة
function addItems(items) {
const list = document.getElementById('list');
const html = items.map(item => `<li>${item}</li>`).join('');
list.innerHTML = html;
}
تفويض الأحداث (Event Delegation)
استخدم تفويض الأحداث لأداء أفضل:
// سيء: إضافة مستمعين لكل عنصر
function attachListeners() {
const buttons = document.querySelectorAll('.item-button');
buttons.forEach(button => {
button.addEventListener('click', handleClick);
});
}
// جيد: تفويض الأحداث
function attachListeners() {
const container = document.getElementById('items-container');
container.addEventListener('click', (e) => {
if (e.target.classList.contains('item-button')) {
handleClick(e);
}
});
}
// يعمل أيضًا مع العناصر المضافة ديناميكيًا
function addNewItem() {
const container = document.getElementById('items-container');
const button = document.createElement('button');
button.className = 'item-button';
button.textContent = 'New Item';
container.appendChild(button);
// لا حاجة لإضافة مستمع - التفويض يتعامل معه!
}
التخفيف والاختناق (Debouncing and Throttling)
تحكم في تكرار تنفيذ الدالة:
// Debounce: التنفيذ بعد تأخير منذ آخر استدعاء
function debounce(func, delay) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
func.apply(this, args);
}, delay);
};
}
// الاستخدام: البحث أثناء الكتابة
const searchInput = document.getElementById('search');
const performSearch = debounce((query) => {
console.log('Searching for:', query);
// استدعاء API هنا
}, 300);
searchInput.addEventListener('input', (e) => {
performSearch(e.target.value);
});
// Throttle: التنفيذ مرة واحدة على الأكثر لكل فترة
function throttle(func, limit) {
let inThrottle;
return function(...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => {
inThrottle = false;
}, limit);
}
};
}
// الاستخدام: حدث التمرير
const handleScroll = throttle(() => {
console.log('Scroll position:', window.scrollY);
}, 100);
window.addEventListener('scroll', handleScroll);
تجنب تسريبات الذاكرة
نظف مستمعي الأحداث والمراجع:
// سيء: تسريب ذاكرة
function attachListener() {
const button = document.getElementById('myButton');
button.addEventListener('click', function() {
// هذا ينشئ closure قد يمنع جمع القمامة
const largeData = new Array(1000000);
console.log(largeData);
});
}
// جيد: تنظيف
class Component {
constructor(element) {
this.element = element;
this.handleClick = this.handleClick.bind(this);
this.element.addEventListener('click', this.handleClick);
}
handleClick() {
console.log('Clicked');
}
destroy() {
// إزالة مستمع الأحداث
this.element.removeEventListener('click', this.handleClick);
// مسح المراجع
this.element = null;
this.handleClick = null;
}
}
// الاستخدام
const component = new Component(document.getElementById('myButton'));
// لاحقًا، عندما لا تكون هناك حاجة للمكون
component.destroy();
RequestAnimationFrame للرسوم المتحركة
استخدم requestAnimationFrame للرسوم المتحركة السلسة:
// سيء: استخدام setTimeout
function animate() {
const element = document.getElementById('box');
let position = 0;
setInterval(() => {
position += 5;
element.style.left = position + 'px';
}, 16); // ~60fps
}
// جيد: استخدام requestAnimationFrame
function animate() {
const element = document.getElementById('box');
let position = 0;
function step() {
position += 5;
element.style.left = position + 'px';
if (position < 500) {
requestAnimationFrame(step);
}
}
requestAnimationFrame(step);
}
// أفضل: مع التحكم في التوقيت
function animate() {
const element = document.getElementById('box');
let start = null;
const duration = 2000; // 2 ثانية
const distance = 500;
function step(timestamp) {
if (!start) start = timestamp;
const progress = timestamp - start;
const percentage = Math.min(progress / duration, 1);
element.style.left = (distance * percentage) + 'px';
if (progress < duration) {
requestAnimationFrame(step);
}
}
requestAnimationFrame(step);
}
6. أفضل ممارسات الأمان
الوقاية من XSS
قم دائمًا بتنظيف مدخلات المستخدم:
// سيء: إدراج مباشر لمدخلات المستخدم
function displayMessage(message) {
document.getElementById('output').innerHTML = message;
// عرضة لـ XSS: <script>alert('hacked')</script>
}
// جيد: استخدم textContent
function displayMessage(message) {
document.getElementById('output').textContent = message;
// علامات السكريبت تُعرض كنص، ولا تُنفذ
}
// جيد: تنظيف HTML
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
function displayMessage(message) {
const sanitized = escapeHtml(message);
document.getElementById('output').innerHTML = sanitized;
}
// أفضل: استخدم مكتبة تنظيف
import DOMPurify from 'dompurify';
function displayMessage(message) {
const clean = DOMPurify.sanitize(message);
document.getElementById('output').innerHTML = clean;
}
التحقق من صحة المدخلات
تحقق دائمًا من صحة المدخلات ونظفها:
// أدوات التحقق من صحة المدخلات
const Validator = {
isEmail(email) {
const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return re.test(email);
},
isUrl(url) {
try {
new URL(url);
return true;
} catch {
return false;
}
},
isNumeric(value) {
return !isNaN(parseFloat(value)) && isFinite(value);
},
isInRange(value, min, max) {
const num = Number(value);
return num >= min && num <= max;
},
hasMinLength(str, length) {
return str.length >= length;
}
};
// الاستخدام
function validateForm(data) {
const errors = {};
if (!Validator.isEmail(data.email)) {
errors.email = 'عنوان بريد إلكتروني غير صالح';
}
if (!Validator.hasMinLength(data.password, 8)) {
errors.password = 'يجب أن تكون كلمة المرور 8 أحرف على الأقل';
}
if (!Validator.isInRange(data.age, 18, 120)) {
errors.age = 'يجب أن يكون العمر بين 18 و 120';
}
return {
isValid: Object.keys(errors).length === 0,
errors
};
}
سياسة أمان المحتوى (CSP)
نفذ ترويسات CSP:
<!-- إضافة علامة CSP meta -->
<meta http-equiv="Content-Security-Policy"
content="default-src 'self';
script-src 'self' https://trusted-cdn.com;
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;">
<!-- أو التعيين في ترويسات HTTP (مفضل) -->
Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted-cdn.com
<!-- تجنب السكريبتات المضمنة -->
<!-- سيء -->
<button onclick="doSomething()">انقر</button>
<!-- جيد -->
<button id="myButton">انقر</button>
<script src="app.js"></script>
// app.js
document.getElementById('myButton').addEventListener('click', doSomething);
7. إمكانية الوصول في JavaScript
خصائص ARIA
أضف خصائص ARIA لقارئات الشاشة:
// إدارة خصائص ARIA
function showNotification(message, type = 'info') {
const notification = document.createElement('div');
notification.className = `notification ${type}`;
notification.textContent = message;
// إضافة خصائص ARIA
notification.setAttribute('role', 'alert');
notification.setAttribute('aria-live', 'polite');
notification.setAttribute('aria-atomic', 'true');
document.body.appendChild(notification);
// إزالة بعد تأخير
setTimeout(() => {
notification.remove();
}, 5000);
}
// تبديل حالة الزر
function toggleButton(button) {
const isPressed = button.getAttribute('aria-pressed') === 'true';
button.setAttribute('aria-pressed', !isPressed);
button.textContent = isPressed ? 'إظهار' : 'إخفاء';
}
// قسم قابل للتوسيع
function toggleSection(button, sectionId) {
const section = document.getElementById(sectionId);
const isExpanded = button.getAttribute('aria-expanded') === 'true';
button.setAttribute('aria-expanded', !isExpanded);
section.hidden = isExpanded;
}
إدارة التركيز
أدر التركيز لتنقل أفضل بلوحة المفاتيح:
// فخ التركيز في المودال
class Modal {
constructor(element) {
this.element = element;
this.focusableElements = null;
this.firstFocusable = null;
this.lastFocusable = null;
this.previousFocus = null;
}
open() {
// حفظ التركيز الحالي
this.previousFocus = document.activeElement;
// إظهار المودال
this.element.style.display = 'block';
this.element.setAttribute('aria-hidden', 'false');
// الحصول على العناصر القابلة للتركيز
this.focusableElements = this.element.querySelectorAll(
'a[href], button:not([disabled]), textarea, input, select'
);
this.firstFocusable = this.focusableElements[0];
this.lastFocusable = this.focusableElements[this.focusableElements.length - 1];
// التركيز على العنصر الأول
this.firstFocusable.focus();
// إضافة مستمعي الأحداث
this.element.addEventListener('keydown', this.handleKeydown.bind(this));
}
close() {
// إخفاء المودال
this.element.style.display = 'none';
this.element.setAttribute('aria-hidden', 'true');
// استعادة التركيز
if (this.previousFocus) {
this.previousFocus.focus();
}
// إزالة مستمعي الأحداث
this.element.removeEventListener('keydown', this.handleKeydown);
}
handleKeydown(e) {
// الإغلاق عند الضغط على Escape
if (e.key === 'Escape') {
this.close();
return;
}
// حبس التركيز مع Tab
if (e.key === 'Tab') {
if (e.shiftKey) {
// Shift + Tab
if (document.activeElement === this.firstFocusable) {
e.preventDefault();
this.lastFocusable.focus();
}
} else {
// Tab
if (document.activeElement === this.lastFocusable) {
e.preventDefault();
this.firstFocusable.focus();
}
}
}
}
}
التنقل بلوحة المفاتيح
نفّذ دعم لوحة المفاتيح:
// قائمة منسدلة مخصصة مع دعم لوحة المفاتيح
class Dropdown {
constructor(button, menu) {
this.button = button;
this.menu = menu;
this.items = Array.from(menu.querySelectorAll('[role="menuitem"]'));
this.currentIndex = -1;
this.button.addEventListener('click', () => this.toggle());
this.button.addEventListener('keydown', (e) => this.handleButtonKeydown(e));
this.menu.addEventListener('keydown', (e) => this.handleMenuKeydown(e));
}
toggle() {
const isOpen = this.menu.getAttribute('aria-hidden') === 'false';
if (isOpen) {
this.close();
} else {
this.open();
}
}
open() {
this.menu.setAttribute('aria-hidden', 'false');
this.button.setAttribute('aria-expanded', 'true');
this.items[0].focus();
this.currentIndex = 0;
}
close() {
this.menu.setAttribute('aria-hidden', 'true');
this.button.setAttribute('aria-expanded', 'false');
this.button.focus();
this.currentIndex = -1;
}
handleButtonKeydown(e) {
if (e.key === 'Enter' || e.key === ' ' || e.key === 'ArrowDown') {
e.preventDefault();
this.open();
}
}
handleMenuKeydown(e) {
switch(e.key) {
case 'Escape':
e.preventDefault();
this.close();
break;
case 'ArrowDown':
e.preventDefault();
this.currentIndex = (this.currentIndex + 1) % this.items.length;
this.items[this.currentIndex].focus();
break;
case 'ArrowUp':
e.preventDefault();
this.currentIndex = this.currentIndex - 1;
if (this.currentIndex < 0) {
this.currentIndex = this.items.length - 1;
}
this.items[this.currentIndex].focus();
break;
case 'Home':
e.preventDefault();
this.currentIndex = 0;
this.items[0].focus();
break;
case 'End':
e.preventDefault();
this.currentIndex = this.items.length - 1;
this.items[this.currentIndex].focus();
break;
case 'Enter':
case ' ':
e.preventDefault();
this.items[this.currentIndex].click();
this.close();
break;
}
}
}
8. مقدمة الاختبار
أساسيات اختبار الوحدة
اكتب كود قابل للاختبار واختبارات أساسية:
// دالة قابلة للاختبار
function calculateDiscount(price, discountPercent) {
if (price < 0 || discountPercent < 0 || discountPercent > 100) {
throw new Error('Invalid input');
}
return price * (discountPercent / 100);
}
// إطار اختبار بسيط
function test(description, testFn) {
try {
testFn();
console.log(`✓ ${description}`);
} catch (error) {
console.error(`✗ ${description}`);
console.error(error.message);
}
}
function assertEquals(actual, expected) {
if (actual !== expected) {
throw new Error(`Expected ${expected} but got ${actual}`);
}
}
function assertThrows(fn, errorMessage) {
try {
fn();
throw new Error('Expected function to throw');
} catch (error) {
if (!error.message.includes(errorMessage)) {
throw new Error(`Expected error message to include "${errorMessage}"`);
}
}
}
// تشغيل الاختبارات
test('يحسب خصم 10% بشكل صحيح', () => {
const result = calculateDiscount(100, 10);
assertEquals(result, 10);
});
test('يحسب خصم 50% بشكل صحيح', () => {
const result = calculateDiscount(200, 50);
assertEquals(result, 100);
});
test('يرمي خطأ للسعر السلبي', () => {
assertThrows(() => calculateDiscount(-100, 10), 'Invalid input');
});
test('يرمي خطأ للخصم غير الصالح', () => {
assertThrows(() => calculateDiscount(100, 150), 'Invalid input');
});
هيكل الاختبار
نظم الاختبارات بنمط ترتيب-تنفيذ-تأكيد:
// فئة User للاختبار
class User {
constructor(name, email) {
this.name = name;
this.email = email;
this.isActive = false;
}
activate() {
this.isActive = true;
}
deactivate() {
this.isActive = false;
}
updateEmail(newEmail) {
if (!newEmail.includes('@')) {
throw new Error('Invalid email');
}
this.email = newEmail;
}
}
// اختبارات باستخدام نمط AAA
test('تفعيل المستخدم يعمل بشكل صحيح', () => {
// الترتيب (Arrange)
const user = new User('John Doe', 'john@example.com');
// التنفيذ (Act)
user.activate();
// التأكيد (Assert)
assertEquals(user.isActive, true);
});
test('إلغاء تفعيل المستخدم يعمل بشكل صحيح', () => {
// الترتيب
const user = new User('Jane Doe', 'jane@example.com');
user.activate();
// التنفيذ
user.deactivate();
// التأكيد
assertEquals(user.isActive, false);
});
test('تحديث البريد الإلكتروني يتحقق من التنسيق', () => {
// الترتيب
const user = new User('Bob Smith', 'bob@example.com');
// التنفيذ والتأكيد
assertThrows(
() => user.updateEmail('invalid-email'),
'Invalid email'
);
});
test('تحديث البريد الإلكتروني يعمل مع بريد صالح', () => {
// الترتيب
const user = new User('Alice Johnson', 'alice@example.com');
const newEmail = 'alice.new@example.com';
// التنفيذ
user.updateEmail(newEmail);
// التأكيد
assertEquals(user.email, newEmail);
});
9. قائمة مراجعة الكود
قبل تقديم الكود
راجع كودك مقابل هذه القائمة:
// قائمة مراجعة جودة الكود
const codeReviewChecklist = {
// الوظيفة
functionality: [
'هل يعمل الكود كما هو مقصود؟',
'هل تتم معالجة الحالات الحدية؟',
'هل تتم معالجة حالات الخطأ؟',
'هل يتم التحقق من صحة المدخلات؟'
],
// نمط الكود
style: [
'هل يتم اتباع اصطلاحات التسمية؟',
'هل المسافات البادئة متسقة؟',
'هل التعليقات واضحة ومفيدة؟',
'هل الكود منسق بشكل صحيح؟'
],
// أفضل الممارسات
bestPractices: [
'هل الدوال صغيرة ومركزة؟',
'هل الكود DRY (لا تكرر نفسك)؟',
'هل تُستخدم الثوابت للأرقام السحرية؟',
'هل يُستخدم const بشكل افتراضي؟'
],
// الأداء
performance: [
'هل معالجات DOM مجمعة؟',
'هل يتم تنظيف مستمعي الأحداث؟',
'هل العمليات المكلفة مخزنة مؤقتًا؟',
'هل يُستخدم التخفيف/الاختناق حيثما كان مناسبًا؟'
],
// الأمان
security: [
'هل يتم تنظيف مدخلات المستخدم؟',
'هل يتم منع ثغرات XSS؟',
'هل البيانات الحساسة محمية؟',
'هل مفاتيح API آمنة؟'
],
// إمكانية الوصول
accessibility: [
'هل تُستخدم خصائص ARIA بشكل صحيح؟',
'هل التنقل بلوحة المفاتيح مدعوم؟',
'هل حالات التركيز مرئية؟',
'هل تباينات الألوان كافية؟'
],
// الاختبار
testing: [
'هل الاختبارات مكتوبة وناجحة؟',
'هل تغطية الاختبار كافية؟',
'هل الحالات الحدية مختبرة؟',
'هل حالات الخطأ مختبرة؟'
],
// التوثيق
documentation: [
'هل الأقسام المعقدة معلقة؟',
'هل وثائق API محدثة؟',
'هل تعليمات README واضحة؟',
'هل التغييرات الجذرية مُلاحظة؟'
]
};
10. البقاء على اطلاع بـ JavaScript
عملية TC39
فهم كيف تتطور JavaScript:
// مراحل مقترحات TC39:
// المرحلة 0: Strawperson - مجرد فكرة
// المرحلة 1: Proposal - يستحق المتابعة
// المرحلة 2: Draft - صياغة دقيقة
// المرحلة 3: Candidate - مكتمل، في انتظار ملاحظات التنفيذ
// المرحلة 4: Finished - سيكون في إصدار ECMAScript التالي
// مثال: إضافات حديثة
// المرحلة 4 (مكتملة):
// - Optional Chaining (?.)
const user = { profile: { name: 'John' } };
console.log(user?.profile?.name); // 'John'
console.log(user?.settings?.theme); // undefined (بدون خطأ)
// - Nullish Coalescing (??)
const value = null ?? 'default'; // 'default'
const zero = 0 ?? 'default'; // 0 (ليس 'default')
// - Private Fields (#)
class BankAccount {
#balance = 0; // حقل خاص
deposit(amount) {
this.#balance += amount;
}
getBalance() {
return this.#balance;
}
}
// - Top-level await
// في الوحدات:
const data = await fetch('/api/data').then(r => r.json());
// - Promise.allSettled()
const results = await Promise.allSettled([
fetch('/api/1'),
fetch('/api/2'),
fetch('/api/3')
]);
// يُرجع جميع النتائج، حتى لو فشل البعض
// موارد للبقاء محدثًا:
// - https://github.com/tc39/proposals
// - https://developer.mozilla.org/en-US/
// - نشرة JavaScript Weekly الإخبارية
// - Twitter: @TC39
// - Can I Use (caniuse.com) لدعم المتصفح
مثال شامل لأفضل الممارسات
إليك مثال كامل يتبع جميع أفضل الممارسات:
// taskManager.js - مثال كامل يتبع أفضل الممارسات
// الثوابت
const TASK_STATUS = {
PENDING: 'pending',
IN_PROGRESS: 'in_progress',
COMPLETED: 'completed'
};
const MAX_TASKS = 100;
// خطأ مخصص
class TaskError extends Error {
constructor(message, code) {
super(message);
this.name = 'TaskError';
this.code = code;
}
}
// أدوات التحقق
const Validator = {
isValidTitle(title) {
return typeof title === 'string' && title.trim().length > 0;
},
isValidStatus(status) {
return Object.values(TASK_STATUS).includes(status);
}
};
// فئة Task مع حقول خاصة
class Task {
#id;
#title;
#status;
#createdAt;
constructor(title) {
if (!Validator.isValidTitle(title)) {
throw new TaskError('عنوان مهمة غير صالح', 'INVALID_TITLE');
}
this.#id = crypto.randomUUID();
this.#title = title;
this.#status = TASK_STATUS.PENDING;
this.#createdAt = new Date();
}
// Getters
get id() { return this.#id; }
get title() { return this.#title; }
get status() { return this.#status; }
get createdAt() { return this.#createdAt; }
// Methods
updateStatus(newStatus) {
if (!Validator.isValidStatus(newStatus)) {
throw new TaskError('حالة غير صالحة', 'INVALID_STATUS');
}
this.#status = newStatus;
}
toJSON() {
return {
id: this.#id,
title: this.#title,
status: this.#status,
createdAt: this.#createdAt.toISOString()
};
}
}
// فئة مدير المهام
class TaskManager {
#tasks;
#listeners;
constructor() {
this.#tasks = new Map();
this.#listeners = new Set();
this.#loadFromStorage();
}
// طرق عامة
addTask(title) {
if (this.#tasks.size >= MAX_TASKS) {
throw new TaskError('تم الوصول إلى الحد الأقصى للمهام', 'MAX_TASKS');
}
try {
const task = new Task(title);
this.#tasks.set(task.id, task);
this.#saveToStorage();
this.#notifyListeners('taskAdded', task);
return task;
} catch (error) {
console.error('فشل في إضافة المهمة:', error);
throw error;
}
}
removeTask(taskId) {
const task = this.#tasks.get(taskId);
if (!task) {
throw new TaskError('المهمة غير موجودة', 'NOT_FOUND');
}
this.#tasks.delete(taskId);
this.#saveToStorage();
this.#notifyListeners('taskRemoved', task);
}
updateTaskStatus(taskId, status) {
const task = this.#tasks.get(taskId);
if (!task) {
throw new TaskError('المهمة غير موجودة', 'NOT_FOUND');
}
task.updateStatus(status);
this.#saveToStorage();
this.#notifyListeners('taskUpdated', task);
}
getTasks() {
return Array.from(this.#tasks.values());
}
getTasksByStatus(status) {
return this.getTasks().filter(task => task.status === status);
}
// نظام الأحداث
on(event, callback) {
this.#listeners.add({ event, callback });
}
off(event, callback) {
this.#listeners.forEach(listener => {
if (listener.event === event && listener.callback === callback) {
this.#listeners.delete(listener);
}
});
}
// طرق خاصة
#notifyListeners(event, data) {
this.#listeners.forEach(listener => {
if (listener.event === event) {
listener.callback(data);
}
});
}
#saveToStorage() {
try {
const data = {
tasks: Array.from(this.#tasks.values()).map(task => task.toJSON())
};
localStorage.setItem('taskManager', JSON.stringify(data));
} catch (error) {
console.error('فشل في الحفظ إلى التخزين:', error);
}
}
#loadFromStorage() {
try {
const data = localStorage.getItem('taskManager');
if (data) {
const parsed = JSON.parse(data);
parsed.tasks.forEach(taskData => {
const task = new Task(taskData.title);
task.updateStatus(taskData.status);
this.#tasks.set(task.id, task);
});
}
} catch (error) {
console.error('فشل في التحميل من التخزين:', error);
}
}
}
// مُتحكم واجهة المستخدم مع إمكانية الوصول
class TaskUI {
constructor(taskManager, container) {
this.taskManager = taskManager;
this.container = container;
this.#setupEventListeners();
this.#render();
}
#setupEventListeners() {
// تفويض الأحداث لقائمة المهام
this.container.addEventListener('click', this.#handleClick.bind(this));
// الاستماع إلى أحداث مدير المهام
this.taskManager.on('taskAdded', () => this.#render());
this.taskManager.on('taskRemoved', () => this.#render());
this.taskManager.on('taskUpdated', () => this.#render());
}
#handleClick(e) {
const target = e.target;
if (target.classList.contains('task-complete')) {
const taskId = target.dataset.taskId;
this.taskManager.updateTaskStatus(taskId, TASK_STATUS.COMPLETED);
} else if (target.classList.contains('task-delete')) {
const taskId = target.dataset.taskId;
this.taskManager.removeTask(taskId);
}
}
#render() {
const tasks = this.taskManager.getTasks();
// استخدم DocumentFragment لأداء أفضل
const fragment = document.createDocumentFragment();
tasks.forEach(task => {
const item = this.#createTaskElement(task);
fragment.appendChild(item);
});
// مسح وإلحاق
this.container.innerHTML = '';
this.container.appendChild(fragment);
// الإعلان لقارئات الشاشة
this.#announceTaskCount(tasks.length);
}
#createTaskElement(task) {
const li = document.createElement('li');
li.className = `task-item task-${task.status}`;
li.setAttribute('role', 'listitem');
const checkbox = document.createElement('button');
checkbox.className = 'task-complete';
checkbox.dataset.taskId = task.id;
checkbox.setAttribute('aria-label', `وضع علامة ${task.title} كمكتمل`);
checkbox.textContent = task.status === TASK_STATUS.COMPLETED ? '✓' : '○';
const title = document.createElement('span');
title.className = 'task-title';
title.textContent = task.title;
const deleteBtn = document.createElement('button');
deleteBtn.className = 'task-delete';
deleteBtn.dataset.taskId = task.id;
deleteBtn.setAttribute('aria-label', `حذف ${task.title}`);
deleteBtn.textContent = '×';
li.appendChild(checkbox);
li.appendChild(title);
li.appendChild(deleteBtn);
return li;
}
#announceTaskCount(count) {
const announcement = document.createElement('div');
announcement.setAttribute('role', 'status');
announcement.setAttribute('aria-live', 'polite');
announcement.className = 'sr-only';
announcement.textContent = `${count} مهام في القائمة`;
document.body.appendChild(announcement);
setTimeout(() => announcement.remove(), 1000);
}
}
// تهيئة التطبيق
function initializeApp() {
try {
const taskManager = new TaskManager();
const container = document.getElementById('task-list');
const taskUI = new TaskUI(taskManager, container);
// معالج نموذج إضافة المهمة
const form = document.getElementById('add-task-form');
const input = document.getElementById('task-input');
form.addEventListener('submit', (e) => {
e.preventDefault();
const title = input.value.trim();
if (title) {
try {
taskManager.addTask(title);
input.value = '';
} catch (error) {
if (error instanceof TaskError) {
alert(error.message);
}
}
}
});
// عرض لتصحيح الأخطاء (إزالة في الإنتاج)
window.taskManager = taskManager;
} catch (error) {
console.error('فشل في تهيئة التطبيق:', error);
}
}
// التشغيل عندما يكون DOM جاهزًا
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initializeApp);
} else {
initializeApp();
}
export { TaskManager, Task, TASK_STATUS };
تمرين المشروع النهائي
أنشئ تطبيقًا كاملاً يتبع جميع أفضل الممارسات:
- بناء تطبيق ملاحظات مع فئات
- تنفيذ عمليات CRUD (إنشاء، قراءة، تحديث، حذف)
- إضافة وظائف البحث والتصفية
- تنفيذ استمرارية localStorage
- إضافة اختصارات لوحة المفاتيح (Ctrl+N لملاحظة جديدة، إلخ)
- جعله قابل للوصول بالكامل (ARIA، التنقل بلوحة المفاتيح)
- تحسين الأداء (تخفيف البحث، تحديثات DOM مجمعة)
- إضافة التحقق من صحة المدخلات ومعالجة الأخطاء
- كتابة اختبارات الوحدة للدوال الأساسية
- توثيق الكود الخاص بك بالتعليقات
الملخص
تهانينا على إكمال دورة أساسيات JavaScript! لقد تعلمت:
- تنظيم الكود: هيكل الملفات الصحيح والوحدات واصطلاحات التسمية
- أفضل ممارسات المتغيرات: استخدام const/let والأسماء ذات المعنى وتجنب العامة
- أفضل ممارسات الدوال: المسؤولية الواحدة والدوال النقية والدوال الصغيرة المركزة
- معالجة الأخطاء: أنماط متسقة وأخطاء مخصصة وتسجيل صحيح
- تحسين الأداء: معالجة DOM وتفويض الأحداث والتخفيف والاختناق
- الأمان: منع XSS والتحقق من المدخلات وCSP
- إمكانية الوصول: خصائص ARIA وإدارة التركيز والتنقل بلوحة المفاتيح
- الاختبار: أساسيات اختبار الوحدة وهيكل الاختبار والتأكيدات
- مراجعة الكود: قائمة التحقق من الجودة وعملية المراجعة
- البقاء محدثًا: عملية TC39 وموارد التعلم
استمر في البرمجة واستمر في التعلم وسعى دائمًا لكتابة كود JavaScript نظيف وقابل للصيانة وفعال!