HTML بتبني الهيكل. CSS بتضيف الشكل. لكن الاتنين دول static — يعني مش بيتغيروا من نفسهم. لو عايز الصفحة "تستجيب"، "تتفاعل"، "تتخذ قرار" — محتاج JavaScript.
JavaScript هي لغة برمجة حقيقية — مش لغة وصف زي HTML أو CSS. ده معناه عندها: متغيرات، شروط، حلقات، دوال، ومنطق حقيقي. هي اللغة الوحيدة اللي بتشتغل في كل متصفح على وجه الأرض بدون استثناء — ده اللي خلاها أهم لغة برمجة في تاريخ الإنترنت.
ظهرت JavaScript سنة 1995 على إيد Brendan Eich في شركة Netscape — وكُتبت في 10 أيام بس! اسمها الأصلي كان Mocha، بعدين LiveScript، وأخيراً JavaScript (اسم تسويقي، مالهاش علاقة فعلية بلغة Java).
النسخة الحالية المعيارية اسمها ECMAScript (ES). بتتطور كل سنة — ES6 (2015) كانت نقلة ضخمة، وإحنا دلوقتي على ES2026 اللي هنتعلمه بالكامل في الكورس ده.
المتصفح عنده محرك JavaScript (في Chrome اسمه V8). لما بتفتح صفحة فيها <script>، المحرك بيعمل الخطوات دي بالترتيب:
- Parsing — المحرك بيقرأ الكود ويحوّله لشجرة (AST) عشان يفهم بنيته
- Compilation — V8 بيحوّل الكود لـ bytecode سريع التنفيذ (Just-In-Time Compilation)
- Execution — الكود بيتنفذ سطر سطر، من فوق لتحت
JavaScript لغة Single-Threaded — يعني عندها "Thread" واحد بس بينفذ كود في وقت واحد. لو سطر كود بياخد وقت طويل (زي حلقة ضخمة)، الصفحة كلها بتتجمد لحد ما يخلص. ده مفهوم هيرجعلنا في وحدة Asynchronous JS لما نتعلم إزاي نتعامل مع عمليات بطيئة من غير ما نجمّد الصفحة.
أهم حاجة فاهمها دلوقتي: كل سطر JavaScript بينفذ بالترتيب، واحد بعد التاني. لو متغير اتعرّف في السطر 5، مش هتقدر تستخدمه في السطر 2 — لأن المحرك لسه ما وصلش لسطر 5 وقتها.
defer. الـ defer بيخلي المتصفح يحمّل الـ HTML كامل أولاً، وبعدين ينفذ الـ JS — كده مش هتحاول تتحكم في عنصر لسه ما اتعملش render.
<!DOCTYPE html> <html> <head> // الطريقة الموصى بها — defer <script src="script.js" defer></script> </head> <body> <h1 id="title">مرحباً</h1> </body> </html>
المتغير هو "صندوق" بتحط فيه قيمة وتديله اسم عشان ترجعله. في JavaScript الحديثة 3 طرق لتعريف متغير — لكن طريقتين بس هتستخدمهم فعلياً.
// ── const: قيمة ثابتة — استخدمها كافتراضي ── // المتغير ده مش ممكن يتغير قيمته بعد التعريف const userName = "Yahya"; const age = 25; // ── let: قيمة ممكن تتغير ── // استخدمها بس لو فعلاً متأكد إن القيمة هتتغير let score = 0; score = 10; // تمام — let بتسمح بالتغيير score = score + 5; // score بقت 15 // ── var: القديمة — تجنبها تماماً ── // موجودة من 1995، فيها مشاكل في الـ Scope (هنشرحها) var oldWay = "تجنبها"; // ── محاولة تغيير const ── const PI = 3.14; PI = 3.15; // ❌ TypeError: Assignment to constant variable
القاعدة الذهبية: ابدأ دايماً بـ const. لو لقيت إن القيمة محتاجة تتغير فعلاً (زي عداد، أو حالة بتتبدل)، غيّرها لـ let. ده بيمنعك من أخطاء كتير — لو غيّرت قيمة كان المفروض ثابتة، JavaScript هتديك error فوراً بدل ما الباج يحصل بصمت.
const بتمنع تغيير "المتغير نفسه" — لكن لو المتغير ده Object أو Array، تقدر تغيّر محتوياته! مثال: const arr = [1,2]; arr.push(3) — ده شغال، لأنك مش بتغيّر arr نفسه، بتغيّر محتواه. هنشرح ده بالتفصيل في وحدة Arrays.
JavaScript عندها 7 أنواع بيانات أساسية (Primitives)، وفاهمين منهم 6 يكفوا تماماً للبداية:
| النوع | مثال | الوصف |
|---|---|---|
String | "Yahya" | نصوص — بين علامتي تنصيص أو backticks |
Number | 25, 3.14, -10 | كل الأرقام — صحيحة وعشرية في نوع واحد |
Boolean | true, false | قيمة منطقية — صح أو غلط فقط |
Undefined | undefined | متغير اتعرّف لكن مالوش قيمة |
Null | null | "لا قيمة" — يتحط قصداً من المبرمج |
Symbol | Symbol() | قيمة فريدة — استخدام متقدم نادر |
// ── String: نصوص ── const name = "Yahya"; // double quotes const city = 'Cairo'; // single quotes — نفس الشيء // ── Number: كل الأرقام نوع واحد ── const age = 25; const price = 19.99; const temperature = -5; // ── Boolean: صح أو غلط ── const isLoggedIn = true; const isAdmin = false; // ── Undefined: ما اتحددش قيمة ── let favoriteColor; console.log(favoriteColor); // undefined // ── Null: "لا شيء" قصداً ── let selectedUser = null; // مفيش مستخدم متحدد حالياً // ── typeof: معرفة نوع المتغير ── console.log(typeof name); // "string" console.log(typeof age); // "number" console.log(typeof isLoggedIn); // "boolean"
"string"
"number"
"boolean"
undefined معناها "المتغير اتعرّف لكن المتصفح مش عارف قيمته" — بيحصل تلقائياً. null معناها "أنا قصداً حطيت إن مفيش قيمة" — بتحطها انت بنفسك. مثال عملي: let selectedUser = null يعني "لسه محدش متحدد"، أما let x من غير قيمة يبقى undefined تلقائياً.
1. محاولة استخدام متغير قبل تعريفه
2. الخلط بين = و ==
= للتعيين (assignment)، == أو === للمقارنة. لو استخدمت = جوّا if قصداً غلط، الكود مش هيديك error — هيدي true دايماً لأنه بيعيّن القيمة 5 ويرجعها. ده من أخطر الأخطاء لأنه صامت.3. محاولة تغيير const
- أنشئ ملف script.js وربطه في الـ HTML بـ
<script src="script.js" defer> - عرّف const لاسمك، مهنتك، وسنة بداية البرمجة
- اطبعهم في Console بـ console.log() وافتح DevTools للتأكد
- جرّب typeof على كل متغير وشوف النتيجة
- جرّب قصداً تغيّر قيمة const وشوف الـ error في Console
- جرّب الفرق بين null وundefined بكتابة console.log لكل واحد
- اعمل console.table بدل console.log لمتغيرات متعددة وشوف الفرق
- "Chrome DevTools Console tutorial" — تعلم كل إمكانيات الـ Console
- "JavaScript defer vs async script" — الفرق المهم بينهم
// ── Arithmetic: العمليات الحسابية ── const sum = 10 + 5; // 15 — جمع const diff = 10 - 5; // 5 — طرح const prod = 10 * 5; // 50 — ضرب const quot = 10 / 5; // 2 — قسمة const rem = 10 % 3; // 1 — باقي القسمة (Modulo) const power = 2 ** 3; // 8 — أس (2 أس 3) // ── Shorthand Operators ── let counter = 0; counter++; // زيادة 1 (counter = counter + 1) counter += 5; // counter = counter + 5 counter -= 2; // counter = counter - 2 // ── Comparison: المقارنة ── // == بيقارن القيمة بس (مع تحويل النوع تلقائياً) ← تجنبه console.log(5 == "5"); // true (يحول "5" لرقم!) // === بيقارن القيمة والنوع مع بعض ← استخدمه دايماً console.log(5 === "5"); // false (Number ≠ String) console.log(5 === 5); // true console.log(10 > 5); // true console.log(10 <= 9); // false console.log(5 !== "5"); // true — "مش بتساوي تماماً" // ── Logical: الربط المنطقي ── const isAdult = true; const hasID = false; console.log(isAdult && hasID); // false — لازم الاتنين true console.log(isAdult || hasID); // true — يكفي واحد true console.log(!isAdult); // false — عكس القيمة
ليه === أفضل من ==؟ الـ == بتعمل "Type Coercion" — يعني بتحاول تحوّل النوعين لنفس النوع قبل المقارنة، وده بيسبب نتائج غريبة زي: "" == false → true، أو null == undefined → true. الـ === بتقارن القيمة والنوع مع بعض، فالنتيجة دايماً متوقعة ومنطقية. استخدم === دايماً إلا لو عندك سبب قوي جداً.
// ── if / else if / else ── const score = 75; if (score >= 90) { console.log("ممتاز"); } else if (score >= 70) { console.log("جيد جداً"); // ← هيطبع ده } else if (score >= 50) { console.log("مقبول"); } else { console.log("راسب"); } // JavaScript بتفحص الشروط بالترتيب — أول شرط true بيتنفذ ويتوقف // ── Ternary Operator: اختصار for if/else بسيط ── const age = 20; const status = age >= 18 ? "بالغ" : "قاصر"; // condition ? value-if-true : value-if-false // ── switch: بديل لـ if عند مقارنة قيمة واحدة بحالات كتير ── const day = "Mon"; switch (day) { case "Sat": case "Sun": console.log("إجازة"); break; // مهم جداً — بدونه الكود هيكمل للـ case الجاي case "Mon": console.log("أول يوم في الأسبوع"); // ← هيطبع ده break; default: console.log("يوم عادي"); }
لو نسيت break، الكود هيكمل ينفذ كل الـ cases اللي بعده تلقائياً (اسمها "Fall-through"). ده غلط شائع جداً — فحص دايماً كل case عندها break إلا لو قصدك فعلاً تستخدم fall-through (زي case "Sat" و"Sun" في المثال).
// ── for: الحلقة الكلاسيكية ── // for (البداية; الشرط; الزيادة) for (let i = 0; i < 5; i++) { console.log(`التكرار رقم ${i}`); } // يطبع: 0, 1, 2, 3, 4 — يبدأ من 0 وينتهي قبل 5 // ── while: تكرار طالما الشرط صحيح ── let count = 0; while (count < 3) { console.log(`Count: ${count}`); count++; // لازم تزود count يدوياً — لو نسيت = infinite loop! } // ── for...of: أفضل طريقة للمرور على Array ── const fruits = ["تفاح", "موز", "عنب"]; for (const fruit of fruits) { console.log(fruit); } // أنظف وأوضح من for العادية — مفيش index لازم تتعامل معاه // ── for...in: المرور على خصائص Object ── const person = { name: "Yahya", age: 25 }; for (const key in person) { console.log(`${key}: ${person[key]}`); } // name: Yahya // age: 25 // ── break و continue ── for (let i = 0; i < 10; i++) { if (i === 3) continue; // يتخطى التكرار ده فقط if (i === 6) break; // يوقف الحلقة كلها console.log(i); } // يطبع: 0, 1, 2, 4, 5
الدالة (Function) هي "آلة" بتاخد مدخلات (Parameters)، تعمل عملية معينة، وترجع نتيجة (Return Value). بدل ما تكرر نفس الكود 10 مرات، بتكتبه مرة واحدة في دالة وتناديها كل ما تحتاجه.
// ── 1. Function Declaration: التعريف الكلاسيكي ── function greet(name) { return `أهلاً، ${name}!`; } console.log(greet("Yahya")); // "أهلاً، Yahya!" // ── 2. Function Expression: الدالة كقيمة لمتغير ── const add = function(a, b) { return a + b; }; console.log(add(3, 5)); // 8 // ── 3. Arrow Function: الأحدث والأكثر استخداماً ── const multiply = (a, b) => { return a * b; }; // لو سطر واحد بس — تقدر تختصر أكتر (implicit return) const square = n => n * n; console.log(square(5)); // 25 — مفيش return ولا {} // ── Default Parameters: قيمة افتراضية لو مفيش argument ── function createUser(name, role = "Member") { return `${name} - ${role}`; } console.log(createUser("Yahya")); // "Yahya - Member" console.log(createUser("Sara", "Admin")); // "Sara - Admin" // ── دالة من غير return: بترجع undefined تلقائياً ── function logMessage(msg) { console.log(msg); // بتطبع بس — مفيش return } const result = logMessage("Hi"); console.log(result); // undefined
Arrow Functions بقت الافتراضي في JS الحديثة لـ 3 أسباب: أقصر وأنظف، مفيهاش مشكلة الـ this المعقدة (هنشرحها في وحدة OOP)، ومناسبة جداً للدوال القصيرة جوّا map/filter/forEach. لكن لسه Function Declaration مفيدة لما عايز الدالة "تترفع" (Hoisting) وتتنادي قبل تعريفها في الكود.
Scope ببساطة هو "المنطقة" اللي المتغير يقدر يتشاف فيها. في JavaScript الحديثة، كل { } بتعمل Scope جديد (Block Scope) — المتغيرات اللي اتعرّفت بـ let/const جواه مش متاحة برّا.
// ── Block Scope ── if (true) { const message = "جوّا الـ if"; console.log(message); // شغال — جوّا نفس الـ Scope } console.log(message); // ❌ ReferenceError: message is not defined // ── Function Scope ── function calculate() { const result = 100; return result; } console.log(result); // ❌ result محبوسة جوّا الدالة فقط // ════════════════════════════════════════ // ── Closure: دالة "بتتذكر" البيئة اللي اتعرّفت فيها ── // ════════════════════════════════════════ function createCounter() { let count = 0; // متغير "خاص" — برّا مش تقدر توصله مباشرة return function() { count++; // الدالة الداخلية "بتتذكر" count حتى بعد ما createCounter خلصت return count; }; } const counter1 = createCounter(); console.log(counter1()); // 1 console.log(counter1()); // 2 — بيفتكر القيمة القديمة! console.log(counter1()); // 3 const counter2 = createCounter(); // counter منفصل تماماً console.log(counter2()); // 1 — مش متأثر بـ counter1
إيه فايدة الـ Closures عملياً؟ بتسمحلك تعمل "متغيرات خاصة" (Private Variables) — count مش متاح من برّا الدالة، الطريقة الوحيدة تغيّره هي عن طريق الدالة اللي رجعتها createCounter. ده أساس الكثير من الـ patterns الاحترافية، وهتستخدمه كتير في الـ event handlers وفي الـ Module Pattern.
الـ Closures من أصعب المفاهيم في JS للمبتدئين. لو حسيت إنها مش واضحة، كمّل في الكورس وارجعلها بعدين — هتلاقيها بتتضح تلقائياً لما تشتغل على event listeners في وحدة DOM. الفهم بييجي بالممارسة مش بالحفظ.
1. Infinite Loop — حلقة بلا نهاية
while(count < 5) { console.log(count) } — نسيت count++ جوّا، فالشرط هيفضل true للأبد. لو حصل ده فعلياً، اقفل التاب أو اضغط Escape في Chrome.2. مناداة دالة ناقصة Parameters
3. استخدام متغير خارج الـ Scope بتاعه
- اكتب دالة getGreeting() ترجع تحية مختلفة حسب الوقت (صباح/مساء) باستخدام if/else ودالة Date()
- اكتب دالة calculateExperience(startYear) ترجع عدد سنين خبرتك من السنة اللي بدأت فيها
- استخدم Arrow Function لدالة بسيطة تحول الاسم لحروف كبيرة (toUpperCase)
- استخدم loop (for...of) لطباعة كل مهاراتك من Array في الـ Console
- اعمل Closure بسيط: دالة createSkillCounter() بتعد كل مهارة بتضيفها
- استخدم switch بدل if/else للتحية حسب اليوم (السبت إجازة، باقي الأيام شغل)
- "JavaScript Date object methods" — getHours, getFullYear, إلخ
- "JavaScript closures explained simply" — شرح بصري للمفهوم
الـ Array هي قائمة مرتبة من القيم — ممكن تحتوي على أي نوع بيانات، وحتى أنواع مختلطة. كل عنصر له "index" بيبدأ من 0.
// ── إنشاء Array ── const skills = ["HTML", "CSS", "JavaScript"]; // index: 0 1 2 // ── الوصول لعنصر ── console.log(skills[0]); // "HTML" console.log(skills[2]); // "JavaScript" console.log(skills.length); // 3 — عدد العناصر // ── إضافة وحذف عناصر ── skills.push("React"); // إضافة في النهاية → ["HTML","CSS","JS","React"] skills.pop(); // حذف آخر عنصر → يرجع "React" ويحذفه skills.unshift("Git"); // إضافة في البداية → ["Git","HTML","CSS","JS"] skills.shift(); // حذف أول عنصر // ── البحث في Array ── console.log(skills.includes("CSS")); // true console.log(skills.indexOf("CSS")); // 1 (مكانها) // ── قص جزء من Array ── const numbers = [1,2,3,4,5]; console.log(numbers.slice(1, 3)); // [2, 3] — من index 1 لحد 3 (مش شامل) // ── ترتيب وعكس ── const nums = [5, 2, 8, 1]; nums.sort((a, b) => a - b); // [1,2,5,8] — تصاعدي nums.reverse(); // [8,5,2,1] — عكس الترتيب // ── join: تحويل Array لنص ── console.log(skills.join(", ")); // "HTML, CSS, JavaScript"
[10, 2, 30].sort() بترجع [10, 2, 30] مش [2, 10, 30]! لأن sort بتقارن كنصوص ("10" أصغر من "2" أبجدياً). لازم تدّيها compare function: .sort((a,b) => a - b) للترتيب الرقمي الصحيح.
دول 3 دوال بتاخد دالة تانية كـ argument وبتشتغل على كل عنصر في الـ Array. اسمهم "Higher-Order Functions". بدل ما تكتب for loop يدوي، بتستخدمهم وبيبقى الكود أوضح وأقصر.
const projects = [ { name: "Portfolio", category: "frontend", year: 2026 }, { name: "API Server", category: "backend", year: 2025 }, { name: "Dashboard", category: "frontend", year: 2026 }, ]; // ── map: تحويل كل عنصر لقيمة جديدة (نفس عدد العناصر) ── const names = projects.map(project => project.name); console.log(names); // ["Portfolio", "API Server", "Dashboard"] // ── filter: استخراج عناصر مطابقة لشرط (عدد أقل أو يساوي) ── const frontendProjects = projects.filter(p => p.category === "frontend"); console.log(frontendProjects); // [Portfolio, Dashboard] // ── reduce: تجميع كل العناصر في قيمة واحدة ── const cart = [{price:100}, {price:250}, {price:50}]; const total = cart.reduce((sum, item) => sum + item.price, 0); // ↑accumulator ↑current ↑initial value console.log(total); // 400 // ── دمج الثلاثة مع بعض — pattern احترافي شائع ── const result = projects .filter(p => p.year === 2026) // فلترة المشاريع الحديثة .map(p => p.name.toUpperCase()); // تحويل لأسماء كابيتال console.log(result); // ["PORTFOLIO", "DASHBOARD"] // ── find: أول عنصر مطابق فقط (مش Array) ── const firstFrontend = projects.find(p => p.category === "frontend"); console.log(firstFrontend); // { name: "Portfolio", ... } // ── some / every: فحص شرط على كل المصفوفة ── console.log(projects.some(p => p.year === 2025)); // true — على الأقل واحد console.log(projects.every(p => p.year >= 2025)); // true — كل العناصر
ليه دول أهم من for loop؟ أولاً: الكود بيوصف "إيه اللي عايز تعمله" مش "إزاي تعمله" — أوضح للقراءة. ثانياً: مفيهاش side effects — مش بتغير الـ Array الأصلي (إلا reduce لو قصدت). ثالثاً: قابلة للدمج (chaining) زي المثال اللي فات. دول هيبقوا أكتر حاجة بتستخدمها في الـ Project Filter بتاع الـ Portfolio لاحقاً.
// ── إنشاء Object ── const developer = { name: "Yahya", title: "Full-Stack Developer", yearsExp: 3, skills: ["HTML", "CSS", "JS"], // methods — دالة جوّا object greet() { return `أنا ${this.name}`; // this تشير للـ object نفسه } }; // ── الوصول للخصائص: طريقتين ── console.log(developer.name); // dot notation — الأكثر شيوعاً console.log(developer["name"]); // bracket notation — لو اسم الخاصية متغير const key = "title"; console.log(developer[key]); // bracket مفيدة هنا — مش ممكن بـ dot // ── إضافة وتعديل خصائص ── developer.location = "Egypt"; // إضافة خاصية جديدة developer.yearsExp = 4; // تعديل قيمة موجودة delete developer.location; // حذف خاصية // ── استدعاء method ── console.log(developer.greet()); // "أنا Yahya" // ── Object.keys / values / entries ── console.log(Object.keys(developer)); // ["name","title","yearsExp","skills"] console.log(Object.values(developer)); // ["Yahya","Full-Stack...",3,[...]] // ── nested objects: object جوّا object ── const portfolio = { owner: { name: "Yahya", contact: { email: "yahya@dev.com" } } }; console.log(portfolio.owner.contact.email); // "yahya@dev.com"
// ── Array Destructuring ── const colors = ["red", "green", "blue"]; // الطريقة القديمة: const first = colors[0]; const second = colors[1]; // الطريقة الحديثة — أسطر واحد: const [primary, secondary, tertiary] = colors; console.log(primary); // "red" // تخطي عنصر const [, , third] = colors; console.log(third); // "blue" // ── Object Destructuring — أكثر استخداماً ── const developer = { name: "Yahya", title: "Developer", age: 25 }; // الطريقة القديمة: const n = developer.name; const t = developer.title; // الطريقة الحديثة — أسماء الخصائص بالظبط: const { name, title } = developer; console.log(name); // "Yahya" // إعادة تسمية أثناء الـ destructuring const { name: devName } = developer; console.log(devName); // "Yahya" // قيمة افتراضية لو الخاصية مش موجودة const { location = "غير محدد" } = developer; console.log(location); // "غير محدد" // ── Destructuring في Parameters — شائع جداً ── function displayUser({ name, title }) { return `${name} - ${title}`; } console.log(displayUser(developer)); // "Yahya - Developer"
// ── Spread: "تفريد" عناصر Array أو Object ── // نسخ Array بدون التأثير على الأصلي const original = [1, 2, 3]; const copy = [...original]; // [1,2,3] — نسخة جديدة منفصلة // دمج Arrays const frontend = ["HTML", "CSS"]; const backend = ["Node", "Express"]; const fullStack = [...frontend, ...backend]; // ["HTML","CSS","Node","Express"] // إضافة عنصر بدون تعديل الأصلي (Immutable pattern) const withNew = [...frontend, "JavaScript"]; // ["HTML","CSS","JavaScript"] — frontend الأصلي لم يتغير // نسخ ودمج Objects const baseConfig = { theme: "dark", lang: "ar" }; const userConfig = { ...baseConfig, theme: "light" }; // { theme: "light", lang: "ar" } — الخاصية المكررة بتاخد آخر قيمة // ── Rest: تجميع عناصر متبقية في Array واحد ── // في Function Parameters — عدد غير محدد من المدخلات function sum(...numbers) { return numbers.reduce((total, n) => total + n, 0); } console.log(sum(1, 2, 3, 4)); // 10 — يقبل أي عدد من الأرقام // مع Destructuring const [first, ...rest] = [10, 20, 30, 40]; console.log(first); // 10 console.log(rest); // [20, 30, 40]
1. تعديل Array أو Object بالخطأ (Mutation)
const modified = [...original]. ده مهم جداً ومن أكتر مصادر الأخطاء صعبة الاكتشاف في JavaScript.2. محاولة الوصول لخاصية في Object غير موجود
user.contact.email وuser.contact أصلاً undefined. الحل: Optional Chaining user.contact?.email — هنشرحها بالتفصيل في وحدة ES6+.3. استخدام map بدل forEach (أو العكس)
- أنشئ Array من Objects لمشاريعك: كل project فيه name, category, description, technologies (array)
- استخدم filter() لاستخراج المشاريع من category معينة فقط
- استخدم map() لاستخراج أسماء كل المشاريع في array منفصل
- استخدم Destructuring لاستخراج name وtechnologies من أول مشروع
- استخدم Spread لعمل نسخة من الـ projects array وإضافة مشروع جديد بدون التأثير على الأصلي
- استخدم reduce() لحساب إجمالي عدد التقنيات المستخدمة عبر كل المشاريع
- رتّب المشاريع حسب السنة باستخدام sort()
- "JavaScript array methods cheat sheet" — مرجع سريع لكل الـ methods
- "shallow copy vs deep copy JavaScript" — مفهوم مهم للمستقبل
لما المتصفح يقرأ ملف HTML، هو مش بيشتغل على النص نفسه — بيحوّله لبنية بيانات في الذاكرة اسمها DOM (Document Object Model). الـ DOM دي عبارة عن شجرة — كل tag في HTML بيتحول لـ "node" في الشجرة دي.
فكّر في الـ HTML بتاعك كـ "مخطط بناء"، والـ DOM هو "البيت الفعلي اللي اتبنى". JavaScript مش بتتعامل مع ملف الـ HTML — هي بتتعامل مع الـ DOM، اللي هو تمثيل حي وقابل للتعديل للصفحة في ذاكرة المتصفح. لو غيّرت الـ DOM، الصفحة بتتغير فوراً قدام عينك — حتى لو ملف الـ HTML الأصلي ما اتغيرش.
مثال: الـ HTML ده:
<body> <header> <h1>Yahya.DEV</h1> </header> <section> <p>مرحباً</p> </section> </body>
بيتحول لشجرة DOM شكلها كده:
├── header
│ └── h1 ("Yahya.DEV")
└── section
└── p ("مرحباً")
كل "صندوق" في الشجرة دي اسمه Element Node. والعلاقات بينهم لها أسماء محددة هتستخدمها كتير:
- Parent — العنصر الأب (header هي parent للـ h1)
- Child — العنصر الابن (h1 هي child لـ header)
- Sibling — عناصر في نفس المستوى (header وsection أخوات)
// ── querySelector: اختيار أول عنصر مطابق ── // نفس syntax الـ CSS Selectors اللي اتعلمتها const heading = document.querySelector("h1"); // أول h1 const heroSection = document.querySelector("#hero"); // بالـ id const firstCard = document.querySelector(".card"); // بالـ class — أول واحد const navLink = document.querySelector("nav a"); // descendant selector // ── querySelectorAll: اختيار كل العناصر المطابقة ── const allCards = document.querySelectorAll(".card"); console.log(allCards.length); // عدد العناصر اللي اتلاقت // allCards بترجع NodeList — زي Array بس مش بالظبط // تقدر تستخدم forEach عليها مباشرة allCards.forEach(card => console.log(card)); // ── تعديل المحتوى ── heading.textContent = "عنوان جديد"; // نص فقط — آمن heading.innerHTML = "<strong>Yahya</strong>"; // يقبل HTML tags // ── تعديل الـ Styles مباشرة ── heading.style.color = "#f0c808"; heading.style.fontSize = "3rem"; // camelCase بدل font-size // ── تعديل Classes — الطريقة المفضلة احترافياً ── heading.classList.add("active"); // إضافة class heading.classList.remove("hidden"); // حذف class heading.classList.toggle("open"); // لو موجود يشيله، لو مش موجود يضيفه console.log(heading.classList.contains("active")); // true/false // ── تعديل Attributes ── const img = document.querySelector("img"); img.setAttribute("src", "new-image.jpg"); console.log(img.getAttribute("alt"));
لو حطيت في innerHTML نص جاي من المستخدم مباشرةً (زي تعليق أو input)، ممكن حد يحط <script> ضار جوّاه (XSS Attack). استخدم textContent دايماً لما بتتعامل مع نص بس، وinnerHTML بس لما واثق من مصدر الـ HTML.
// ── إنشاء عنصر جديد من الصفر ── const newCard = document.createElement("div"); newCard.classList.add("project-card"); newCard.textContent = "مشروع جديد"; // ── إضافته للصفحة ── const container = document.querySelector("#projects"); container.appendChild(newCard); // يضيفه في الآخر // أو الطريقة الأحدث: container.append(newCard); // مرنة أكتر — تقبل نص وعدة عناصر // ── بناء card كاملة بـ HTML structure ── function createProjectCard(project) { const card = document.createElement("article"); card.classList.add("project-card"); // أفضل طريقة: بناء العناصر منفصلة ثم دمجها const title = document.createElement("h3"); title.textContent = project.name; const desc = document.createElement("p"); desc.textContent = project.description; card.append(title, desc); // إضافة عناصر متعددة مرة واحدة return card; } // ── استخدامها على array من المشاريع ── const projects = [ { name: "Portfolio", description: "صفحتي الشخصية" }, { name: "Todo App", description: "تطبيق مهام" } ]; projects.forEach(project => { const card = createProjectCard(project); container.append(card); }); // ── حذف عناصر ── newCard.remove(); // الطريقة الحديثة — مباشرة وبسيطة // ── إفراغ container من كل محتواه قبل إعادة الملء ── container.innerHTML = ""; // شائعة، لكن أبطأ من remove لكل عنصر
كتابة container.innerHTML += "..." جوّا forEach بطيئة جداً للأداء — لأن المتصفح بيعيد بناء كل الـ DOM من جديد في كل تكرار. الأفضل: ابني العناصر بـ createElement وappend مرة واحدة، أو جمّع الـ HTML في string وحطه مرة واحدة في الآخر.
لحد دلوقتي كل الكود بتاعنا كان بينفذ مرة واحدة من فوق لتحت. لكن المواقع الحقيقية مش كده — لازم تستجيب لما المستخدم يضغط زرار، يكتب، يعمل scroll. هنا بييجي دور الـ Events.
// ── addEventListener: الطريقة الصحيحة والوحيدة احترافياً ── const button = document.querySelector("#submitBtn"); button.addEventListener("click", function() { console.log("تم الضغط!"); }); // ── الوصول لمعلومات الـ Event نفسه ── button.addEventListener("click", event => { console.log(event.target); // العنصر اللي اتدوس عليه event.preventDefault(); // يمنع السلوك الافتراضي (مهم في forms) }); // ── أشهر أنواع الـ Events ── button.addEventListener("click", handleClick); input.addEventListener("input", handleTyping); // كل ما المستخدم يكتب حرف form.addEventListener("submit", handleSubmit); // إرسال الفورم window.addEventListener("scroll", handleScroll); // التمرير window.addEventListener("load", handleLoad); // لما الصفحة تخلص تحميل input.addEventListener("focus", handleFocus); // لما الـ input يتفعّل input.addEventListener("blur", handleBlur); // لما يفقد الفوكس // ── مثال عملي: Dark Mode Toggle ── const themeToggle = document.querySelector("#themeToggle"); themeToggle.addEventListener("click", () => { document.documentElement.classList.toggle("light-mode"); });
الـ Event Loop — إزاي JS بتستمع لكل حاجة في وقت واحد رغم إنها Single-Threaded؟ المتصفح عنده "Call Stack" بتنفذ الكود، و"Web APIs" بتستقبل الـ events (زي click وscroll) من برّا، و"Callback Queue" بتستنى فيها الـ event handlers لحد ما الـ Call Stack تفضى. الـ Event Loop بتراقب باستمرار: "الـ Stack فاضية؟ يبقى هات أول حاجة من الـ Queue ونفذها". كده JS بتحس إنها "بتستمع" لكل حاجة، رغم إنها فعلياً بتنفذ سطر واحد في كل لحظة.
onclick في الـ HTML بيخلط المنطق بالهيكل (نفس مشكلة inline CSS). addEventListener بيخليك تفصل JS تماماً، وتقدر تضيف أكتر من listener على نفس العنصر، وتشيله بسهولة بـ removeEventListener وقت ما تحتاج.
لما تضغط على عنصر جوّا عنصر تاني، الـ Event مش بتتفعل على العنصر ده بس — هي "بتطلع" (bubble) لكل الـ parents بتاعته كمان. ده اسمه Event Bubbling.
// ── المشكلة: عايز listener على كل li جوّا قائمة ── // الطريقة الساذجة — أداء سيء وغير عملية: const items = document.querySelectorAll("li"); items.forEach(item => { item.addEventListener("click", handleClick); }); // المشكلة: لو أضفت li جديد بعدين، مش هيكون عنده listener! // ── الحل الاحترافي: Event Delegation ── // حط listener واحد بس على الـ parent، واستفد من الـ Bubbling const list = document.querySelector("#projectsList"); list.addEventListener("click", event => { // event.target هو العنصر الفعلي اللي اتدوس عليه if (event.target.tagName === "LI") { console.log(`دوست على: ${event.target.textContent}`); } }); // ✓ بيشتغل حتى مع عناصر تتضاف ديناميكياً بعدين // ✓ listener واحد بدل مئات — أداء أفضل بكتير // ── closest(): إيجاد أقرب parent مطابق ── // مفيدة لو الكليك حصل على عنصر جوّا li (زي icon) list.addEventListener("click", event => { const clickedItem = event.target.closest("li"); if (clickedItem) { clickedItem.classList.toggle("selected"); } }); // ── إيقاف الـ Bubbling لو احتجت ── button.addEventListener("click", event => { event.stopPropagation(); // يمنع الـ Event من الطلوع للـ parents });
1. محاولة الوصول لعنصر قبل ما الصفحة تحمّل
2. النسيان إن querySelectorAll بترجع NodeList مش Array حقيقي
const cardsArray = Array.from(cards) أو [...cards].3. تكرار إضافة Event Listeners
4. this جوّا Arrow Function في Event Listener
- اعمل Hamburger Menu: زرار يضيف class "open" للـ nav menu عند الضغط (toggle)
- اعمل Project Filter: أزرار categories (All, Frontend, Backend) بتفلتر الـ project cards باستخدام classList
- استخدم createElement لبناء project cards من الـ Array اللي عملته في Task 3 وعرضها ديناميكياً
- استخدم Event Delegation على الـ project container بدل listener لكل card
- أضف smooth scroll للـ nav links باستخدام scrollIntoView()
- اعمل "Back to Top" button يظهر بس لما scroll يتجاوز معين
- "JavaScript event delegation explained" — فيديوهات توضيحية بصرية
- "MDN scrollIntoView" — للـ smooth scroll الاحترافي
- "Chrome DevTools Elements panel debugging"
كنت شايف `${variable}` في كل الدروس اللي فاتت — دي Template Literals، وهنشرحها بالتفصيل دلوقتي.
const name = "Yahya"; const age = 25; // ── الطريقة القديمة: جمع نصوص بـ + ── const oldWay = "اسمي " + name + " وعمري " + age; // مزعجة، صعبة القراءة، سهلة الغلط في المسافات // ── الطريقة الحديثة: Template Literals بـ backticks ── const newWay = `اسمي ${name} وعمري ${age}`; // واضحة، أسهل، تقدر تحط أي expression جوّا ${} // ── تقدر تحط expressions كاملة مش بس متغيرات ── const message = `بعد سنتين هتبقى عمرك ${age + 2}`; const status = `أنت ${age >= 18 ? "بالغ" : "قاصر"}`; // ── Multi-line Strings — أسطر متعددة بدون \n ── const card = ` <div class="card"> <h3>${name}</h3> <p>العمر: ${age}</p> </div> `; // مفيدة جداً لبناء HTML structures كاملة من JS // ── استخدام عملي: بناء project card ── function renderProject(project) { return ` <article class="project-card"> <h3>${project.name}</h3> <p>${project.description}</p> </article> `; }
const user = { name: "Yahya", social: { github: "yahya-dev" // لاحظ: مفيش instagram هنا } }; // ── المشكلة بدون Optional Chaining ── console.log(user.social.instagram.followers); // ❌ Uncaught TypeError: Cannot read properties of undefined // لأن instagram أصلاً undefined، فمحاولة الوصول لـ .followers بتطيح // ── الحل: Optional Chaining ?. ── console.log(user.social?.instagram?.followers); // undefined — مفيش error! بتوقف بهدوء لو أي خطوة undefined // ── استخدامها مع Methods ── const obj = {}; obj.someMethod?.(); // لو الـ method مش موجودة، مش هتطيح error // ── استخدامها مع Arrays ── const data = { items: null }; console.log(data.items?.[0]); // undefined — مش error // ════════════════════════════════════ // ── Nullish Coalescing: قيمة بديلة افتراضية ── // ════════════════════════════════════ const username = null; // المشكلة باستخدام || (OR العادي): const display1 = username || "زائر"; // لو username = 0 أو "" (نص فارغ)، برضو هيستخدم "زائر"! ده غلط أحياناً // الحل: ?? بتفحص null أو undefined فقط، مش كل "falsy values" const display2 = username ?? "زائر"; console.log(display2); // "زائر" — username فعلاً null const score = 0; console.log(score || 100); // 100 ← خطأ! score=0 لسه قيمة صحيحة console.log(score ?? 100); // 0 ← صح! score مش null ولا undefined
دول من أهم إضافات JS الحديثة: Optional Chaining (ES2020) وNullish Coalescing (ES2020) بيحلوا مشكلتين شائعتين جداً: الخوف من undefined properties، والخلط بين "مفيش قيمة" و"القيمة صفر/فاضية". استخدمهم دايماً بدل الفحص اليدوي الطويل بـ if statements متعددة.
// ── Set: مجموعة بدون عناصر مكررة ── const numbers = [1, 2, 2, 3, 3, 3]; const uniqueNumbers = [...new Set(numbers)]; console.log(uniqueNumbers); // [1, 2, 3] — حذف التكرارات تلقائياً! const tags = new Set(); tags.add("javascript"); tags.add("frontend"); tags.add("javascript"); // مش هيتضاف — موجود بالفعل console.log(tags.size); // 2 console.log(tags.has("frontend")); // true // ── Map: زي Object بس بميزات أقوى ── const userRoles = new Map(); userRoles.set("Yahya", "Admin"); userRoles.set("Sara", "Member"); console.log(userRoles.get("Yahya")); // "Admin" console.log(userRoles.size); // 2 // المرور على كل عناصر الـ Map userRoles.forEach((role, user) => { console.log(`${user}: ${role}`); });
Map أفضل لما تحتاج: مفاتيح من أي نوع (مش بس نصوص)، ترتيب مضمون للعناصر، عدد كبير من القراءة/الكتابة المتكررة (أداء أفضل). Object يفضل أفضل في حالات بسيطة وأكتر شيوعاً. للمبتدئين: استخدم Object كافتراضي، وارجع لـ Map لو احتجت ميزاته تحديداً.
زي ما قسّمنا CSS لملفات منفصلة، JavaScript الحديثة بتسمحلك تقسّم الكود لـ modules — كل ملف مسؤول عن حاجة معينة، وتقدر تستوردها في ملفات تانية.
// ════════════════════════════════════ // ملف: utils.js // ════════════════════════════════════ // ── Named Export: تصدير أكتر من حاجة بالاسم ── export function formatDate(date) { return date.toLocaleDateString("ar-EG"); } export const SITE_NAME = "Yahya.DEV"; // ── Default Export: حاجة واحدة رئيسية للملف ── export default function initApp() { console.log("التطبيق بدأ"); } // ════════════════════════════════════ // ملف: main.js // ════════════════════════════════════ // استيراد named exports بأسمائهم بالظبط import { formatDate, SITE_NAME } from "./utils.js"; // استيراد default export — تقدر تسميه أي اسم import initApp from "./utils.js"; console.log(SITE_NAME); // "Yahya.DEV" console.log(formatDate(new Date())); // تاريخ منسّق initApp(); // ════════════════════════════════════ // ملف: index.html — لازم type="module" // ════════════════════════════════════ // <script src="main.js" type="module"></script>
لو فتحت الـ HTML مباشرة بـ file:// في المتصفح، الـ Modules هتدّيك CORS error. لازم تشغّل الملف عن طريق local server (زي Live Server extension في VS Code) عشان الـ import/export تشتغل صح.
- راجع دالة renderProject بتاعتك واستخدم Template Literals بدل أي جمع نصوص بـ +
- استخدم Optional Chaining في أي مكان بتوصل فيه لـ nested properties من بيانات المشاريع
- استخدم Set لاستخراج كل التقنيات الفريدة (بدون تكرار) المستخدمة في مشاريعك
- قسّم الكود لملفين: utils.js (دوال مساعدة) وmain.js (المنطق الرئيسي) باستخدام import/export
- استخدم Map لتخزين عدد المشاريع في كل category
- استخدم Nullish Coalescing لقيم افتراضية لو بيانات مشروع ناقصة
- "JavaScript ES6 features overview" — قائمة شاملة بكل المميزات الحديثة
- "VS Code Live Server extension" — لتشغيل Modules محلياً
كل كود كتبناه لحد دلوقتي كان Synchronous — يعني بينفذ سطر سطر، كل سطر بينتظر اللي قبله يخلص. لكن بعض العمليات بطيئة طبيعياً (تحميل صورة، طلب بيانات من سيرفر) — لو خلّينا JS تستنى ليهم بشكل sync، الصفحة كلها هتتجمد.
// ── Synchronous: كل سطر بيستنى اللي قبله ── console.log("1"); console.log("2"); console.log("3"); // النتيجة: 1, 2, 3 — بالترتيب بالظبط // ── Asynchronous: setTimeout كمثال بسيط ── console.log("1"); setTimeout(() => { console.log("2 (بعد ثانيتين)"); }, 2000); // 2000 ميلي ثانية = ثانيتين console.log("3"); // النتيجة: 1, 3, 2 (بعد ثانيتين) ← مش 1,2,3! // JavaScript ما استنتش الـ setTimeout — كملت تنفيذ سطر "3" // وبعدين لما الوقت يخلص، نفّذت الـ callback
ليه ده مهم؟ لو عملية زي fetch بيانات من سيرفر كانت Synchronous، الصفحة هتتجمد تماماً (المستخدم مش هيقدر يضغط ولا يscroll) لحد ما الرد يوصل — وده ممكن ياخد ثواني. بفضل الـ Asynchronous، JavaScript بتكمل تنفيذ باقي الكود وتتعامل مع رد السيرفر "لما يجهز" من غير ما توقف حاجة تانية.
// ── Callback: دالة بتتمرر كـ argument لتنفذ لاحقاً ── function fetchUserData(userId, callback) { setTimeout(() => { const userData = { id: userId, name: "Yahya" }; callback(userData); // تنفيذ الـ callback لما البيانات تجهز }, 1000); } fetchUserData(1, userData => { console.log(userData); // بعد ثانية: { id: 1, name: "Yahya" } }); // ════════════════════════════════════ // ── المشكلة: Callback Hell ── // لما عندك عمليات متتالية، كل واحدة محتاجة نتيجة اللي قبلها // ════════════════════════════════════ fetchUser(1, user => { fetchPosts(user.id, posts => { fetchComments(posts[0].id, comments => { fetchLikes(comments[0].id, likes => { console.log(likes); // 😱 "هرم الموت" — كل سطر متداخل أعمق وأعمق // صعب القراءة، صعب الصيانة، صعب الـ error handling }); }); }); });
كل ما العمليات المتسلسلة تزيد، الكود بيتداخل أعمق وأعمق ("Pyramid of Doom"). صعب تتبع الأخطاء، صعب تضيف منطق جديد، صعب تقرأ الترتيب. ده بالظبط اللي خلّى JavaScript تطوّر Promises كحل.
الـ Promise هي object بتمثل عملية async هتخلص لاحقاً. عندها 3 حالات: pending (لسه شغالة)، fulfilled (نجحت)، rejected (فشلت).
// ── إنشاء Promise ── function fetchUserData(userId) { return new Promise((resolve, reject) => { setTimeout(() => { if (userId > 0) { resolve({ id: userId, name: "Yahya" }); // نجاح } else { reject(new Error("Invalid user ID")); // فشل } }, 1000); }); } // ── استخدام الـ Promise: then / catch ── fetchUserData(1) .then(user => { console.log("نجح:", user); return user.id; // القيمة المُرجعة تنتقل للـ then الجاي }) .then(id => { console.log("الـ ID:", id); }) .catch(error => { console.log("حصل خطأ:", error.message); }) .finally(() => { console.log("انتهت العملية — نجحت أو فشلت"); }); // ── حل Callback Hell باستخدام Promise Chaining ── fetchUser(1) .then(user => fetchPosts(user.id)) .then(posts => fetchComments(posts[0].id)) .then(comments => console.log(comments)) .catch(error => console.log(error)); // مسطح وواضح — مفيش تداخل عميق زي Callback Hell // ── Promise.all: انتظار عدة Promises مع بعض ── Promise.all([fetchUser(1), fetchUser(2), fetchUser(3)]) .then(users => console.log(users)); // array بكل النتائج // أسرع من تنفيذهم واحد واحد — بتشتغل بالتوازي
Async/Await مش طريقة جديدة فعلياً — هي "صياغة أجمل" (syntactic sugar) فوق الـ Promises. بتخلي الكود الـ async يبان زي كود عادي متسلسل، وده بيسهّل القراءة جداً.
// ── async function: تعريف دالة تقدر تستخدم await جواها ── async function getUserData(userId) { try { // await بيوقف تنفيذ الدالة دي بس (مش الصفحة كلها) // لحد ما الـ Promise يخلص، وبيرجع القيمة مباشرة const user = await fetchUserData(userId); console.log("نجح:", user); return user; } catch (error) { // try/catch بتمسك أي error بدل .catch() console.log("خطأ:", error.message); } } getUserData(1); // ── مقارنة مباشرة: Promises vs Async/Await ── // بـ Promises: function loadDashboard() { return fetchUser(1) .then(user => fetchPosts(user.id)) .then(posts => fetchComments(posts[0].id)) .catch(err => console.log(err)); } // نفس المنطق بـ Async/Await — أوضح بكثير: async function loadDashboard() { try { const user = await fetchUser(1); const posts = await fetchPosts(user.id); const comments = await fetchComments(posts[0].id); return comments; } catch (err) { console.log(err); } } // بيقرأ زي كود عادي بالترتيب — مفيش .then متتالية // ── تنفيذ متوازي بـ await + Promise.all ── async function loadAllUsers() { const users = await Promise.all([ fetchUser(1), fetchUser(2), fetchUser(3) ]); return users; }
// ── fetch: طلب GET بسيط ── async function getPosts() { try { const response = await fetch("https://api.example.com/posts"); // لازم تتأكد إن الطلب نجح فعلياً if (!response.ok) { throw new Error(`HTTP Error: ${response.status}`); } // تحويل الرد لـ JSON — برضو عملية async const posts = await response.json(); console.log(posts); return posts; } catch (error) { console.log("فشل التحميل:", error.message); } } // ── إرسال POST request (مثل Contact Form) ── async function submitContactForm(formData) { try { const response = await fetch("https://formspree.io/f/your-id", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(formData) // تحويل Object لـ JSON string }); if (response.ok) { console.log("تم إرسال الرسالة بنجاح!"); } } catch (error) { console.log("فشل الإرسال:", error); } } // ── استخدام عملي مع Contact Form ── const form = document.querySelector("#contactForm"); form.addEventListener("submit", async event => { event.preventDefault(); // منع إعادة تحميل الصفحة const formData = { name: form.name.value, email: form.email.value, message: form.message.value }; await submitContactForm(formData); form.reset(); // تفريغ الفورم بعد الإرسال });
fetch بشكل افتراضي مش بترفض الـ Promise حتى لو السيرفر رجّع error (زي 404 أو 500) — هي بترفض بس لو فيه مشكلة شبكة فعلية. لازم تفحص response.ok يدوياً للتأكد إن الطلب نجح فعلاً، وإلا الكود هيكمل وكأن كل حاجة تمام رغم وجود error.
1. نسيان await
2. استخدام await خارج async function
3. عدم التعامل مع الأخطاء (Unhandled Promise Rejection)
- اربط Contact Form بتاع الـ Portfolio بـ Formspree.io (أو أي خدمة مشابهة) باستخدام fetch
- استخدم async/await وtry/catch للتعامل مع الإرسال والأخطاء
- اعرض رسالة "تم الإرسال بنجاح" أو "حصل خطأ" حسب نتيجة الطلب باستخدام DOM manipulation
- عطّل زرار الإرسال (disabled) أثناء انتظار الرد عشان تمنع إرسال مكرر
- أضف loading spinner يظهر أثناء الإرسال ويختفي بعدها
- استخدم Promise.all لتحميل بيانات وهمية متعددة (mock APIs) بالتوازي
- "Formspree setup tutorial" — ربط فورم بإيميلك مجاناً
- "JSONPlaceholder fake API" — API وهمي للتجربة قبل الـ deployment
- "JavaScript loading spinner CSS"
تخيل عندك مشاريع كتير في الـ Portfolio. كل مشروع عنده نفس الخصائص (name, description) ونفس السلوكيات (render, filter). بدل ما تكرر نفس الكود لكل مشروع، OOP بتسمحلك تعمل "قالب" (Class) وتنشئ منه نسخ (Instances) بسهولة.
4 مبادئ أساسية في OOP: Encapsulation (تجميع البيانات والدوال المرتبطة في وحدة واحدة)، Abstraction (إخفاء التفاصيل المعقدة وإظهار الواجهة البسيطة بس)، Inheritance (كائن جديد بيرث خصائص من كائن أساسي)، Polymorphism (نفس الدالة بتتصرف مختلف حسب الكائن). هنطبّق المبادئ دي عملياً في الدروس الجاية.
// ── تعريف Class ── class Project { // constructor: بيتنفذ تلقائياً عند إنشاء instance جديد constructor(name, category, description) { this.name = name; this.category = category; this.description = description; this.likes = 0; // قيمة افتراضية } // methods: دوال متاحة لكل instance render() { return `<h3>${this.name}</h3><p>${this.description}</p>`; } addLike() { this.likes++; return this.likes; } } // ── إنشاء Instances من الـ Class ── const portfolio = new Project("Portfolio", "frontend", "صفحتي الشخصية"); const api = new Project("API", "backend", "سيرفر بيانات"); console.log(portfolio.name); // "Portfolio" console.log(portfolio.render()); // "<h3>Portfolio</h3>..." portfolio.addLike(); console.log(portfolio.likes); // 1 console.log(api.likes); // 0 — منفصل تماماً عن portfolio // ── Getters & Setters: التحكم في الوصول للخصائص ── class User { constructor(name) { this._name = name; // _ بتدل على إنها "خاصة" بالاتفاق (مش إلزامي) } get name() { return this._name.toUpperCase(); } set name(newName) { if (newName.length < 2) { throw new Error("الاسم قصير جداً"); } this._name = newName; } } const user = new User("yahya"); console.log(user.name); // "YAHYA" — get بيتنفذ تلقائياً user.name = "Sara"; // set بيتنفذ تلقائياً
// ── Class أساسية ── class Project { constructor(name) { this.name = name; } render() { return `<h3>${this.name}</h3>`; } } // ── Class بترث من Project وتضيف ميزات جديدة ── class FeaturedProject extends Project { constructor(name, badge) { super(name); // لازم تنادي constructor الـ parent class this.badge = badge; } // Override: إعادة تعريف method موجودة في الـ parent render() { const baseHTML = super.render(); // استدعاء النسخة الأصلية من الـ parent return baseHTML + `<span class="badge">${this.badge}</span>`; } } const topProject = new FeaturedProject("Portfolio", "⭐ مميز"); console.log(topProject.render()); // "<h3>Portfolio</h3><span class='badge'>⭐ مميز</span>" console.log(topProject instanceof Project); // true — ورثت منها // ── Private Fields: حماية حقيقية (مش بالاتفاق زي _) ── class BankAccount { #balance = 0; // # بتخلي الخاصية خاصة فعلياً — مش متاحة من برّا deposit(amount) { this.#balance += amount; } getBalance() { return this.#balance; } } const account = new BankAccount(); account.deposit(100); console.log(account.getBalance()); // 100 console.log(account.#balance); // ❌ SyntaxError: Private field
this قيمتها بتتغير حسب إزاي الدالة اتنادت — مش حسب فين اتعرّفت. ده بالظبط اللي خلّى Arrow Functions تتصرف غريب في Event Listeners (شفناها في مطب الوحدة الرابعة).
// ── this جوّا method في Object: بتشير للـ object نفسه ── const user = { name: "Yahya", greet() { console.log(this.name); // "Yahya" — this = user } }; user.greet(); // "Yahya" // ── this جوّا Regular Function في Event Listener: بتشير للعنصر ── button.addEventListener("click", function() { console.log(this); // <button> نفسه — العنصر اللي اتدوس this.classList.toggle("active"); // عملياً مفيدة جداً }); // ── this جوّا Arrow Function: ماعندهاش this خاص بيها ── // بتاخده من الـ scope الخارجي (lexical this) button.addEventListener("click", () => { console.log(this); // window أو undefined — مش button! }); // ── المشكلة الشائعة: this جوّا Class methods كـ callback ── class Counter { constructor() { this.count = 0; } increment() { this.count++; console.log(this.count); } } const counter = new Counter(); button.addEventListener("click", counter.increment); // ❌ this بيتلخبط هنا! // لما addEventListener ينادي increment، this مش هتبقى counter // ── الحل: Arrow Function wrapper أو .bind() ── button.addEventListener("click", () => counter.increment()); // ✓ الحل الأسهل // أو: button.addEventListener("click", counter.increment.bind(counter)); // ✓ بديل
القاعدة الذهبية لـ this: في Regular Functions، this بتتحدد "وقت النداء" — لو نديت العنصر.method()، this = العنصر. في Arrow Functions، this بتتحدد "وقت التعريف" — بتاخدها من المكان اللي اتكتبت فيه. الفهم ده بيحل 90% من اللخبطة حوالين this.
- أنشئ Class Project بـ constructor فيه name, category, description, technologies
- أضف method render() بترجع HTML structure للمشروع كـ Template Literal
- حوّل بيانات مشاريعك من Array of Objects العادية إلى Array من instances الـ Project
- اعمل Class تانية FeaturedProject بترث من Project وتضيف badge مميز
- أضف Private Field #views للعد الخاص بكل مشروع
- استخدم getter لحساب عدد التقنيات تلقائياً من array technologies
- "JavaScript Classes vs Functions" — متى تستخدم كل أسلوب
- "JavaScript this keyword visual explanation"
// ── ❌ كود سيء: أسماء غامضة ── function calc(a, b, c) { return a * b * (c / 100); } // ── ✓ كود نظيف: أسماء واضحة وقصدية ── function calculateDiscountedPrice(price, quantity, discountPercent) { return price * quantity * (discountPercent / 100); } // ── ❌ دالة بتعمل أكتر من حاجة واحدة ── function processUser(user) { user.name = user.name.trim(); validateEmail(user.email); saveToDatabase(user); sendWelcomeEmail(user); // 4 مسؤوليات مختلفة في دالة واحدة — صعب الاختبار والصيانة } // ── ✓ Single Responsibility: كل دالة مسؤولة عن حاجة واحدة ── function cleanUserName(user) { return { ...user, name: user.name.trim() }; } function registerUser(user) { const cleanedUser = cleanUserName(user); validateEmail(cleanedUser.email); saveToDatabase(cleanedUser); sendWelcomeEmail(cleanedUser); } // ── ❌ Magic Numbers: أرقام بدون معنى واضح ── if (user.age > 17) { /* ... */ } // ── ✓ استخدم constants بأسماء واضحة ── const LEGAL_AGE = 18; if (user.age >= LEGAL_AGE) { /* ... */ }
5 قواعد ذهبية للـ Clean Code: ١) أسماء متغيرات ودوال واضحة وصفية (مش x وy وtemp). ٢) كل دالة تعمل حاجة واحدة بس (Single Responsibility). ٣) تجنب التكرار — لو كتبت نفس الكود مرتين، اعمله دالة. ٤) Comments بتشرح "ليه" مش "إيه" — الكود نفسه المفروض يشرح "إيه". ٥) constants بدل أرقام عشوائية في الكود.
Layout Thrashing بيحصل لما الكود بيقرأ ويكتب خصائص الـ DOM بالتبادل بشكل متكرر، وده بيجبر المتصفح يعيد حساب الـ Layout مرات كتير غير ضرورية.
// ── ❌ Layout Thrashing: قراءة وكتابة متبادلة جوّا loop ── const boxes = document.querySelectorAll(".box"); boxes.forEach(box => { const height = box.offsetHeight; // قراءة (Read) — تجبر المتصفح يحسب layout box.style.height = height + 10 + "px"; // كتابة (Write) // كل تكرار: قراءة ← كتابة ← قراءة ← كتابة — بطيء جداً مع عدد كبير }); // ── ✓ الحل: افصل القراءة عن الكتابة ── const heights = [...boxes].map(box => box.offsetHeight); // كل القراءات مرة واحدة boxes.forEach((box, i) => { box.style.height = heights[i] + 10 + "px"; // كل الكتابات بعدها }); // ── ❌ تعديل DOM داخل loop مباشرة ── const list = document.querySelector("#list"); for (const item of items) { const li = document.createElement("li"); li.textContent = item; list.append(li); // كل append بيعيد render للصفحة! } // ── ✓ استخدم DocumentFragment: تعديل واحد بس للـ DOM ── const fragment = document.createDocumentFragment(); for (const item of items) { const li = document.createElement("li"); li.textContent = item; fragment.append(li); // بيتجمع في الذاكرة، مش بيأثر على الصفحة } list.append(fragment); // re-render واحد بس لكل العناصر مع بعض
// ── المشكلة: scroll/input بتتفعل مئات المرات في الثانية ── window.addEventListener("scroll", () => { expensiveCalculation(); // بتتنفذ كتير جداً — بطيء جداً! }); // ── Debounce: تنفّذ الدالة بس بعد ما المستخدم "يهدأ" ── function debounce(func, delay) { let timeoutId; return function(...args) { clearTimeout(timeoutId); // يلغي أي تنفيذ سابق مجدول timeoutId = setTimeout(() => func(...args), delay); }; } const searchInput = document.querySelector("#search"); const debouncedSearch = debounce(query => { console.log(`بحث عن: ${query}`); // بتتنفذ بس بعد 500ms من آخر كتابة }, 500); searchInput.addEventListener("input", e => { debouncedSearch(e.target.value); }); // مفيدة جداً لـ: search boxes، form validation، resize events // ── Throttle: تنفّذ الدالة مرة كل فترة محددة بالظبط ── function throttle(func, limit) { let inThrottle; return function(...args) { if (!inThrottle) { func(...args); inThrottle = true; setTimeout(() => inThrottle = false, limit); } }; } const throttledScroll = throttle(() => { console.log("تتنفذ كل 200ms كحد أقصى"); }, 200); window.addEventListener("scroll", throttledScroll); // مفيدة لـ: scroll events، animations مرتبطة بالـ scroll
Debounce: لما عايز تنتظر "المستخدم يخلص" (search box، form validation). الدالة بتتنفذ مرة واحدة بس بعد فترة هدوء. Throttle: لما عايز تنفيذ منتظم بمعدل ثابت بغض النظر عن التكرار (scroll animations). الدالة بتتنفذ بانتظام كل فترة زمنية محددة.
// ── LocalStorage: حفظ بيانات تفضل موجودة حتى بعد إغلاق المتصفح ── // تخزين قيمة — لازم تكون string localStorage.setItem("theme", "dark"); // قراءة قيمة const savedTheme = localStorage.getItem("theme"); console.log(savedTheme); // "dark" // تخزين Object — لازم تحوّله لـ JSON String الأول const userPrefs = { theme: "dark", fontSize: 16 }; localStorage.setItem("prefs", JSON.stringify(userPrefs)); // استرجاعه — لازم تحوّله من JSON String لـ Object تاني const retrieved = JSON.parse(localStorage.getItem("prefs")); console.log(retrieved.theme); // "dark" // حذف عنصر أو كل البيانات localStorage.removeItem("theme"); localStorage.clear(); // حذف كل حاجة — استخدمها بحذر // ── مثال عملي: Dark Mode بـ localStorage ── function applyTheme() { const theme = localStorage.getItem("theme") ?? "dark"; document.documentElement.setAttribute("data-theme", theme); } applyTheme(); // تطبيق الثيم المحفوظ عند تحميل الصفحة themeToggle.addEventListener("click", () => { const current = document.documentElement.getAttribute("data-theme"); const newTheme = current === "dark" ? "light" : "dark"; document.documentElement.setAttribute("data-theme", newTheme); localStorage.setItem("theme", newTheme); // حفظ الاختيار }); // ── Regular Expressions: فحص صيغة نص (مهم لـ Form Validation) ── const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; console.log(emailRegex.test("yahya@dev.com")); // true console.log(emailRegex.test("invalid-email")); // false // استخدام عملي في validation function validateEmail(email) { return emailRegex.test(email); }
كتابة Regular Expressions معقدة من الصفر مهارة متقدمة. في الواقع العملي، المحترفين بيستخدموا regex patterns جاهزة وموثوقة (زي الـ email pattern اللي فوق) من مصادر موثوقة بدل ما يخترعوها من الصفر. المهم تفهم إزاي تستخدمها بـ .test() مش إزاي تبنيها بالكامل.
- اعمل Dark/Light Mode Toggle كامل: زرار، تغيير data-theme، حفظ الاختيار في localStorage
- أضف Email Validation بـ RegEx على الـ Contact Form قبل الإرسال
- استخدم debounce() على أي input field عندك (لو فيه search مثلاً)
- راجع كل دوالك وتأكد من أسماء واضحة (Clean Code) وكل دالة مسؤولة عن حاجة واحدة
- استخدم throttle على scroll event لو عندك أي scroll-based effect
- اعمل DocumentFragment لو بتبني أكتر من عنصر مرة واحدة
- "JavaScript debounce throttle visual difference"
- "Chrome DevTools Performance tab tutorial" — لقياس الأداء فعلياً
- "Email regex pattern JavaScript" — أنماط موثوقة جاهزة
data.js (بيانات المشاريع كـ Classes) ← utils.js (دوال مساعدة: debounce، formatDate) ← main.js (المنطق الرئيسي والـ event listeners). استخدم import/export بينهم.التاسكات اللي فاتت كانت بتوجهك خطوة بخطوة. التاسك ده مختلف — هندّيك المتطلبات بس، وانت اللي هتقرر إزاي تنفذها. ده الاختبار الحقيقي لفهمك، مش حفظك.
- Portfolio كامل ومتكامل: HTML semantic + CSS احترافي + JavaScript تفاعلي بالكامل، منشور على GitHub Pages
- كل تفاعل بدون أخطاء في الـ Console — افتح DevTools وتأكد إن مفيش أي error أحمر
- Performance جيد — استخدم Lighthouse في Chrome DevTools واوصل لـ 85+ في كل المعايير
- كود نظيف ومنظم في ملفات منفصلة منطقياً بـ Modules
- Responsive كامل — يشتغل صح على موبايل وتابلت وديسكتوب
- لو علقت، ارجع للدروس السابقة قبل ما تدور على الحل جاهز — الرجوع للأساسيات بيقوّي فهمك
- "how to debug JavaScript errors Chrome DevTools" — مهارة الـ debugging أهم من حفظ الحلول
- شارك المشروع النهائي على LinkedIn أو GitHub — أول خطوة في بناء حضورك كمبرمج