لماذا JavaScript الحديث مختلف عن JavaScript القديم؟
JavaScript تطور بشكل كبير منذ ES6 (2015). الكود الحديث أقصر، أوضح، وأكثر قوة. إذا كنت لا تزال تكتب var بدلاً من let و const، فأنت تستخدم أدوات عمرها عقد من الزمن.
في هذا الدليل ستتعلم المفاهيم المتقدمة التي يستخدمها المطورون المحترفون يومياً مع أمثلة عملية لكل مفهوم.
المتغيرات: let و const بدل var
// var: نطاق الدالة (function scope) - مشاكل كثيرة
var name = "أحمد";
var name = "محمد"; // لا خطأ! يعيد التعريف
// let: نطاق الكتلة (block scope) - قابل للتغيير
let age = 25;
age = 26; // ✅ يعمل
// let age = 30; // ❌ خطأ! لا يمكن إعادة التعريف
// const: نطاق الكتلة - ثابت
const PI = 3.14;
// PI = 3.15; // ❌ خطأ!
// لكن const مع الكائنات يسمح بتعديل المحتوى
const user = { name: "أحمد" };
user.name = "محمد"; // ✅ يعمل! المرجع ثابت لكن المحتوى قابل للتعديل
// القاعدة: استخدم const دائماً، let عند الحاجة للتغيير، var أبداً
الدوال السهمية (Arrow Functions)
// الطريقة القديمة
function add(a, b) {
return a + b;
}
// الدالة السهمية
const add = (a, b) => a + b;
// مع أكثر من سطر
const calculateTotal = (price, tax) => {
const taxAmount = price * tax;
return price + taxAmount;
};
// مع معامل واحد (بدون أقواس)
const double = n => n * 2;
// تطبيق عملي: فلترة مصفوفة
const products = [
{ name: "هاتف", price: 500 },
{ name: "سماعات", price: 50 },
{ name: "لابتوب", price: 1200 }
];
const expensive = products.filter(p => p.price > 100);
// النتيجة: [{name: "هاتف", price: 500}, {name: "لابتوب", price: 1200}]
التفكيك (Destructuring)
// تفكيك الكائنات
const user = { name: "سارة", age: 28, city: "الدار البيضاء" };
// الطريقة القديمة
const name = user.name;
const age = user.age;
// التفكيك
const { name, age, city } = user;
// مع إعادة التسمية
const { name: userName, age: userAge } = user;
// مع قيم افتراضية
const { name, country = "المغرب" } = user;
// تفكيك المصفوفات
const colors = ["أحمر", "أزرق", "أخضر"];
const [first, second, third] = colors;
// تخطي عناصر
const [, , last] = colors; // last = "أخضر"
// في معاملات الدوال (شائع جداً في React)
const showUser = ({ name, age }) => {
console.log(`${name} عمره ${age}`);
};
showUser(user);
عامل الانتشار والباقي (Spread & Rest)
// Spread: نشر عناصر مصفوفة أو كائن
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const combined = [...arr1, ...arr2]; // [1, 2, 3, 4, 5, 6]
// نسخ كائن مع تعديل
const user = { name: "أحمد", age: 25 };
const updatedUser = { ...user, age: 26 }; // {name: "أحمد", age: 26}
// Rest: تجميع الباقي
const [first, ...rest] = [1, 2, 3, 4, 5];
// first = 1, rest = [2, 3, 4, 5]
// في الدوال
const sum = (...numbers) => numbers.reduce((a, b) => a + b, 0);
sum(1, 2, 3, 4); // 10
القوالب النصية (Template Literals)
const name = "فاطمة";
const age = 30;
// الطريقة القديمة
const msg = "مرحباً " + name + "، عمرك " + age + " سنة";
// القالب النصي
const msg = `مرحباً ${name}، عمرك ${age} سنة`;
// متعدد الأسطر
const html = `
${name}
العمر: ${age}
سنة الميلاد: ${2026 - age}
`;
// مع تعبيرات
const price = 100;
const tax = 0.2;
const total = `المجموع: ${(price * (1 + tax)).toFixed(2)} د.م`;
الوعود و Async/Await
أهم مفهوم للتعامل مع العمليات غير المتزامنة مثل جلب البيانات من API:
// Promises - الوعود
fetch('https://api.example.com/users')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('خطأ:', error));
// Async/Await - أسهل وأوضح
async function getUsers() {
try {
const response = await fetch('https://api.example.com/users');
const data = await response.json();
console.log(data);
return data;
} catch (error) {
console.error('خطأ:', error);
}
}
// تطبيق عملي: جلب بيانات الطقس
async function getWeather(city) {
try {
const response = await fetch(
`https://api.openweathermap.org/data/2.5/weather?q=${city}&lang=ar&units=metric&appid=YOUR_KEY`
);
if (!response.ok) {
throw new Error('المدينة غير موجودة');
}
const data = await response.json();
return {
city: data.name,
temp: data.main.temp,
description: data.weather[0].description,
humidity: data.main.humidity
};
} catch (error) {
console.error(error.message);
}
}
// طلبات متوازية
async function getDashboardData() {
const [users, products, orders] = await Promise.all([
fetch('/api/users').then(r => r.json()),
fetch('/api/products').then(r => r.json()),
fetch('/api/orders').then(r => r.json())
]);
return { users, products, orders };
}
دوال المصفوفات الحديثة
const products = [
{ name: "هاتف", price: 500, category: "إلكترونيات" },
{ name: "كتاب", price: 30, category: "كتب" },
{ name: "لابتوب", price: 1200, category: "إلكترونيات" },
{ name: "قميص", price: 80, category: "ملابس" }
];
// filter: فلترة
const electronics = products.filter(p => p.category === "إلكترونيات");
// map: تحويل
const names = products.map(p => p.name);
// ["هاتف", "كتاب", "لابتوب", "قميص"]
const withDiscount = products.map(p => ({
...p,
discountPrice: p.price * 0.9
}));
// reduce: تجميع
const total = products.reduce((sum, p) => sum + p.price, 0);
// 1810
// find: إيجاد عنصر واحد
const laptop = products.find(p => p.name === "لابتوب");
// some / every: تحقق
const hasExpensive = products.some(p => p.price > 1000); // true
const allCheap = products.every(p => p.price < 500); // false
// سلسلة من العمليات
const result = products
.filter(p => p.category === "إلكترونيات")
.map(p => ({ name: p.name, finalPrice: p.price * 0.85 }))
.sort((a, b) => a.finalPrice - b.finalPrice);
// [{name: "هاتف", finalPrice: 425}, {name: "لابتوب", finalPrice: 1020}]
الوحدات (Modules)
// utils.js - تصدير
export const formatPrice = (price) => `${price.toFixed(2)} د.م`;
export const capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1);
export default function logger(msg) {
console.log(`[${new Date().toLocaleTimeString()}] ${msg}`);
}
// app.js - استيراد
import logger, { formatPrice, capitalize } from './utils.js';
logger("بدء التطبيق");
console.log(formatPrice(99.5)); // "99.50 د.م"
Optional Chaining و Nullish Coalescing
// Optional Chaining (?.) - تجنب أخطاء undefined
const user = {
name: "أحمد",
address: { city: "الرباط" }
};
// بدون optional chaining (خطر!)
// const zip = user.address.zipCode.toString(); // ❌ خطأ!
// مع optional chaining
const zip = user.address?.zipCode?.toString(); // undefined بدون خطأ
const phone = user.contact?.phone; // undefined
// Nullish Coalescing (??) - قيمة افتراضية
const count = 0;
const result1 = count || 10; // 10 (|| يعتبر 0 falsy)
const result2 = count ?? 10; // 0 (?? فقط null/undefined)
// تطبيق عملي
const displayName = user.nickname ?? user.name ?? "مستخدم مجهول";
مشروع تطبيقي: تطبيق إدارة المهام
// نظام إدارة مهام باستخدام كل المفاهيم
class TaskManager {
#tasks = []; // خاصية خاصة
addTask(title, priority = 'متوسط') {
const task = {
id: Date.now(),
title,
priority,
done: false,
createdAt: new Date()
};
this.#tasks.push(task);
return task;
}
completeTask(id) {
const task = this.#tasks.find(t => t.id === id);
if (task) task.done = true;
return task;
}
getStats() {
const total = this.#tasks.length;
const done = this.#tasks.filter(t => t.done).length;
const byPriority = this.#tasks.reduce((acc, t) => {
acc[t.priority] = (acc[t.priority] || 0) + 1;
return acc;
}, {});
return { total, done, pending: total - done, byPriority };
}
getPending() {
return this.#tasks
.filter(t => !t.done)
.sort((a, b) => {
const order = { 'عالي': 0, 'متوسط': 1, 'منخفض': 2 };
return order[a.priority] - order[b.priority];
});
}
}
// الاستخدام
const tm = new TaskManager();
tm.addTask("بناء واجهة المستخدم", "عالي");
tm.addTask("كتابة التوثيق", "منخفض");
tm.addTask("اختبار API", "عالي");
console.log(tm.getStats());
console.log(tm.getPending());
أنماط التصميم الشائعة في JavaScript
أنماط التصميم هي حلول مجربة لمشاكل متكررة في تطوير البرمجيات. في JavaScript الحديث هناك عدة أنماط أساسية يجب على كل مطور معرفتها وفهم متى يستخدمها.
أول نمط هو نمط الوحدة (Module Pattern) الذي يسمح بتغليف المتغيرات والدوال في نطاق خاص يمنع التلوث العام ويوفر واجهة عامة محددة للتفاعل مع الوحدة. هذا النمط تطور مع إضافة وحدات ES6 التي توفر نفس الوظيفة بشكل أنظف عبر كلمتي import و export. يُنصح دائماً بتقسيم الكود إلى وحدات صغيرة مركّزة حيث كل وحدة مسؤولة عن مهمة واحدة محددة وهو ما يُعرف بمبدأ المسؤولية الواحدة.
النمط الثاني هو نمط المراقب (Observer Pattern) الذي يسمح لعدة كائنات بالاشتراك في أحداث كائن آخر والاستجابة عند حدوث تغييرات. هذا النمط مستخدم بكثرة في أطر العمل الحديثة مثل React و Vue حيث تتفاعل المكونات تلقائياً عند تغيّر البيانات. في JavaScript الأصلي يمكن تنفيذه باستخدام CustomEvent و addEventListener أو ببناء نظام أحداث مخصص بسيط.
النمط الثالث هو نمط الوعود المتسلسلة (Promise Chaining) والذي تطور إلى async/await في JavaScript الحديث. هذا النمط ضروري للتعامل مع العمليات غير المتزامنة بشكل نظيف ومقروء. القاعدة الأساسية هي دائماً معالجة الأخطاء باستخدام try/catch مع async/await أو catch مع Promises لتجنب الأخطاء الصامتة التي قد تتسبب في سلوك غير متوقع للتطبيق.
النمط الرابع هو نمط المصنع (Factory Pattern) الذي يوفر طريقة لإنشاء كائنات دون تحديد الفئة الدقيقة مسبقاً. هذا مفيد جداً عندما يعتمد نوع الكائن على معامل أو شرط معين مثل إنشاء أنواع مختلفة من العناصر بناءً على بيانات قادمة من الخادم. يُستخدم هذا النمط كثيراً في بناء مكونات واجهة المستخدم الديناميكية.
إدارة الذاكرة وتحسين الأداء
فهم كيفية إدارة الذاكرة في JavaScript أمر ضروري لبناء تطبيقات سريعة ومستقرة خاصة التطبيقات طويلة التشغيل مثل تطبيقات الصفحة الواحدة التي قد يبقى المستخدم فيها لساعات دون إعادة تحميل الصفحة.
أول مفهوم يجب فهمه هو جامع القمامة (Garbage Collector) الذي يحرر الذاكرة المخصصة للكائنات التي لم تعد مرجعية. لكن جامع القمامة لا يمكنه تحرير الذاكرة إذا كان هناك مرجع لا يزال يشير إلى الكائن حتى لو لم يعد مستخدماً فعلياً. هذه المشكلة تُعرف بتسريب الذاكرة وهي من أصعب الأخطاء اكتشافاً وإصلاحاً.
من أشهر أسباب تسريب الذاكرة في JavaScript عدم إزالة مستمعي الأحداث عند حذف العناصر من الصفحة وتخزين مراجع للعناصر المحذوفة في المتغيرات العامة واستخدام setInterval بدون تنظيف وإنشاء Closures تحتفظ بمراجع لكائنات كبيرة لم تعد ضرورية. الحل هو دائماً تنظيف المستمعين والمؤقتات عند الانتهاء منها واستخدام WeakMap و WeakRef عندما لا تحتاج لمنع جامع القمامة من تحرير الكائنات.
أما بالنسبة لتحسين الأداء فهناك عدة تقنيات أساسية. أولاً التأخير والاختناق (Debounce and Throttle) لتقليل تكرار تنفيذ الدوال المكلفة مثل البحث أثناء الكتابة أو التمرير. ثانياً التحميل الكسول (Lazy Loading) لتأخير تحميل الموارد والمكونات حتى تكون مرئية أو مطلوبة فعلياً. ثالثاً تقنية الافتراضية (Virtualization) لعرض القوائم الطويلة حيث يتم عرض العناصر المرئية فقط وإعادة استخدامها عند التمرير بدلاً من إنشاء آلاف عناصر DOM.
أمان تطبيقات JavaScript
أمان التطبيقات يجب أن يكون أولوية من اليوم الأول وليس فكرة لاحقة. في JavaScript هناك عدة نقاط ضعف شائعة يجب معرفتها والوقاية منها.
أخطر هذه الثغرات هي Cross-Site Scripting أو XSS حيث يستطيع المهاجم حقن كود JavaScript خبيث في صفحتك يتم تنفيذه في متصفحات المستخدمين الآخرين. الوقاية تكون بعدم استخدام innerHTML مع بيانات المستخدم أبداً واستخدام textContent بدلاً منه وتنظيف جميع المدخلات قبل عرضها واستخدام Content Security Policy في رؤوس HTTP لتحديد مصادر السكريبتات المسموحة.
الثغرة الثانية هي تزوير الطلبات عبر المواقع CSRF حيث يخدع المهاجم المستخدم لإرسال طلب غير مقصود إلى موقعك. الوقاية تكون باستخدام رموز CSRF فريدة مع كل نموذج والتحقق من رأس Origin و Referer واستخدام خاصية SameSite في ملفات تعريف الارتباط لمنع إرسالها مع طلبات من مواقع أخرى.
أدوات تطوير JavaScript الحديثة
النظام البيئي لأدوات JavaScript واسع ومتنوع وإليك أهم الأدوات التي يحتاجها كل مطور في سير عمله اليومي.
مدير الحزم npm و yarn و pnpm: هذه الأدوات تدير تبعيات المشروع وتسمح بتثبيت المكتبات والأطر بأمر واحد. npm يأتي مع Node.js ويُعد الأكثر استخداماً بينما yarn يوفر سرعة أعلى وتثبيتاً أكثر موثوقية. أما pnpm فيوفر مساحة تخزين عن طريق مشاركة الحزم بين المشاريع بدلاً من تكرارها.
أدوات البناء Vite و Webpack: تحوّل هذه الأدوات الكود الحديث إلى كود متوافق مع المتصفحات وتجمع الملفات وتحسّنها للإنتاج. Vite هو الخيار الحديث الأسرع الذي يستخدم ES modules أصلياً مما يوفر تحديثاً فورياً أثناء التطوير بينما Webpack أقدم وأكثر نضجاً مع نظام إضافات ضخم.
مدقق الكود ESLint و منسّقه Prettier: ESLint يكتشف الأخطاء المحتملة وينفذ معايير كتابة الكود بينما Prettier ينسّق الكود تلقائياً بشكل متسق. استخدامهما معاً يضمن جودة كود عالية وشكل متسق عبر جميع ملفات المشروع وأعضاء الفريق.
TypeScript: هو امتداد لـ JavaScript يضيف أنواع بيانات ثابتة اختيارية. يكتشف الأخطاء قبل التشغيل ويحسّن الإكمال التلقائي في المحرر ويجعل الكود أكثر وضوحاً وسهولة في الصيانة. أصبح TypeScript معياراً في المشاريع الاحترافية وتدعمه جميع الأطر الرئيسية مثل React و Vue و Angular و Node.js.
أدوات الاختبار Jest و Vitest: الاختبار التلقائي يمنع الأخطاء ويعطي ثقة عند إجراء تغييرات. Jest هو الأكثر شعبية ويوفر بيئة اختبار شاملة بينما Vitest أسرع ومتوافق مع Vite. كلاهما يدعم اختبارات الوحدة والتكامل والمحاكاة.
ملخص المهارات المكتسبة
constوletبدلvarلتجنب مشاكل النطاق- الدوال السهمية لكود أقصر وأوضح
- التفكيك لاستخراج البيانات من الكائنات والمصفوفات بسهولة
- Spread/Rest لنسخ ودمج البيانات
- Async/Await للتعامل مع العمليات غير المتزامنة
- دوال المصفوفات (map, filter, reduce) لمعالجة البيانات
- Optional Chaining لتجنب أخطاء undefined
- الوحدات لتنظيم الكود في ملفات منفصلة
الخطوة التالية: طبّق هذه المفاهيم ببناء مشروع كامل — تطبيق todo list أو متجر إلكتروني — باستخدام كل التقنيات التي تعلمتها.