⚡ الدوال (Functions): القلب النابض للبرمجة

🔹 1. ما هي الـ Function؟

الدالة هي عبارة عن "كتلة من الكود" (Code Block) مصممة لتنفيذ مهمة محددة. بدلاً من كتابة نفس الكود 10 مرات، تكتبه مرة واحدة وتستدعيه وقتما شئت.

💡 المبدأ: Write Once, Use Many (اكتب مرة، استخدم كثيراً).
🥤 التشبيه: الدالة مثل "الخلاط". تضع فيه مكونات (مدخلات)، يقوم هو بالعمل (معالجة)، ثم يعطيك العصير (مخرجات).

🔹 2. Function Declaration (تعريف الدالة)

دي الطريقة التقليدية لكتابة الدالة في JavaScript.


function sayHello() {
    console.log("Hello");
    // السطر ده هيطبع كلمة Hello في الـ console
}

📌 هنا:

  • function : كلمة محجوزة لتعريف الدالة.
  • sayHello : اسم الدالة اللي اخترناه.
  • الكود جوه الأقواس { } هو اللي بيتنفّذ عند الاستدعاء.

🔹 3. Calling the Function (استدعاء الدالة)

الدالة لا تعمل أبداً وهي "نائمة" إلا إذا قمت بمناداتها (استدعائها).

sayHello(); // هنا بنقول للبرنامج: نفّذ الدالة الآن!
💡

بدون الأقواس ()، أنت تشير للدالة فقط ولا تشغلها. الأقواس هي "زر التشغيل".

function sayHello() {
      console.log("مرحباً بك في موقعنا!");
    }
    
    sayHello(); // استدعاء: سيطبع الجملة

🔹 4. Parameters & Arguments

الدالة ممكن تستقبل بيانات عشان تكون مرنة وتنفذ مهام مختلفة لنفس الكود.

function greet(name) { // name هنا هو Parameter
            console.log("Hello " + name);
        }
        
        greet("Eslam"); // "Eslam" هنا هو Argument

📌 الفكرة:

  • Parameter : المتغير اللي بيستقبل القيمة (وعاء فارغ).
  • Argument : القيمة الحقيقية اللي بنبعتها فعليًا للدالة.
🥛

Parameter: هو الكوب الفاضي.

💧

Argument: هو الماية اللي بتحطها في الكوب.

🔹 5. return vs console.log (فرق مهم جدًا)

📢 console.log

يستخدم للعرض فقط (للمبرمج) لتصحيح الأخطاء.

function sum(a, b) {
   console.log(a + b); // عرض فقط
  }

❌ المشكلة: لا يمكن تخزين النتيجة أو استخدامها في عملية حسابية أخرى.

💰 return

يُرجع قيمة حقيقية للكود لكي نستخدمها لاحقاً.

function sum(a, b) {
 return a + b; // إرجاع الناتج
 }
let result = sum(3, 5); // result = 8  

✅ الميزة: النتيجة أصبحت ملكك، يمكنك ضربها، جمعها، أو تخزينها.

📌قاعدة

بمجرد وصول الدالة لسطر return، تتوقف فوراً وتخرج. أي كود مكتوب بعدها لا يتم تنفيذه نهائياً.

🔹 6. Examples 🚀

تعال نطبق اللي اتعلمناه في مثال حقيقي: حساب السعر النهائي لمنتج في متجر إلكتروني بعد إضافة الضريبة.

function calculateFinalPrice(price, tax) {
    // price = سعر المنتج الحقيقي
    // tax = نسبة الضريبة (مثلاً 0.14 للـ 14%)
            
    let finalPrice = price + (price * tax);
    return finalPrice; // بنرجع السعر النهائي عشان نستخدمه في الفاتورة
}
        
let productPrice = calculateFinalPrice(100, 0.14);  //  price=100, tax=0.14 
console.log("السعر النهائي: " + productPrice);  // الناتج = 114
المدخلات (Params): السعر والضريبة (بيانات متغيرة)
المعالجة (Logic): معادلة حسابية بسيطة
المخرجات (Return): القيمة اللي هندفعها فعلياً

🔹 7. Arrow Functions (مهمة جدًا جدًا)

Arrow Functions هي طريقة مختصرة لكتابة الدوال.

الشكل العادي

function sum(a, b) {
    return a + b;
  }

Arrow Function

const sum = (a, b) => {
    return a + b;
  };

اختصار أكتر

const sum = (a, b) => a + b;

📌 مميزات Arrow Functions:

  • ✅ كود أقصر
  • ✅ أسهل قراءة
  • ✅ مستخدمة في React و Vue و Angular

🔹 8. Scope (Local / Global)

Global Scope

متغير متعرف بره الدالة.

let siteName = "JS Guide";
function showName() {
    console.log(siteName);
    // يقدر يشوف المتغير
 }

Local Scope

متغير جوه الدالة.

function testScope() {
    let x = 10;
    console.log(x);
  }
// console.log(x); ❌ Error

📌 السبب: المتغير x عايش جوه الدالة بس، بره الدالة مش موجود.

🔹 9. Default Parameters (قيم افتراضية)

لو المستخدم مبعتش قيمة.

function welcome(name = "Guest") {
    return "Welcome " + name;
    }
welcome();        // Welcome Guest
welcome("Islam"); // Welcome Islam

✔️ تمنع Errors | ✔️ تجعل الدالة أذكى

🔹 10. Callback Function (مقدمة بسيطة)

Callback = دالة بتتبعت لدالة تانية.

function runTask(task) {
    task();
    // هنا بنشغّل الدالة اللي جت
 }
        
runTask(function () {
    console.log("Task Done");
  });

📌 تستخدم في: click events | setTimeout | async code

❌ أخطاء شائعة (تجنبها!)

🧠 الملخص

🏆 تحدي الدوال (Final Challenge)

اكتب دالة مختصرة (Arrow Function) باسم getDiscount تحسب السعر بعد خصم 20%.

// المطلوب منك:
// 1. الدالة تستقبل السعر (price) كـ Parameter.
// 2. ترجع السعر بعد الخصم (السعر مضروب في 0.8).
// 3. استخدم الـ Arrow Function للاختصار.
// مثلاً: const getDiscount = (price) => ...

⚡ النصوص (Strings): لغة البيانات

🔹 يعني إيه String؟

الـ String هو نص. أي حاجة بين العلامات دي تعتبر String:

" " أو ' ' أو ` `
let name = "Eslam";
let city = 'Cairo';
let message = `Hello`;

🔹 الفرق بين أنواع علامات التنصيص

1️⃣ " " و ' '

  • ✅ الاتنين نفس الوظيفة تماماً.
  • ✅ يخزن بها حرف ' '

2️⃣ ` ` (Backticks)

مهمة جدًا لأنها تسمح بـ:

  • ✅ كتابة متغير داخل النص.
  • ✅ كتابة النص في أكتر من سطر.
let name = "Eslam";
let text = `Hello ${name}`; // دي اسمها: Template Literals

🔹 جمع النصوص (Concatenation)

الطريقة القديمة

let result = first + " " + second;

الطريقة الحديثة (الأفضل)

let result = `${first} ${second}`;

⚠️ مهم جدًا

"10" + 5 // "105"
لو في String → العملية تبقى دمج مش جمع حسابي.

🔹 خصائص الـ String

📌 الطول (length):

let name = "Eslam";
    console.log(name.length); // 5

📌 الوصول لحرف معين:

let word = "Hello";
    console.log(word[0]); // H
    console.log(word[1]); // e

💡 أول حرف دايمًا index = 0

🔹 أهم String Methods (لازم تحفظهم)

1️⃣ toUpperCase() : تحويل النص لحروف كبيرة
2️⃣ toLowerCase() : تحويل لحروف صغيرة

"hello".toUpperCase(); // HELLO // تحويل النص لحروف كبيرة toUpperCase
"HELLO".toLowerCase(); // hello // تحويل لحروف صغيرة  toLowerCase 

3️⃣ trim(): إزالة المسافات من البداية والنهاية.

let text = "  hello  ";
console.log(text.trim()); // "hello"

💡 مهم جدًا في: إدخال المستخدم والفورمات.

4️⃣ includes() : يتأكد هل النص يحتوي على جزء معين

let text = "Hello World";
console.log(text.includes("World")); // true
           

5️⃣ startsWith(): بتتحقق هل النص بيبدأ بجزء معين ولا لأ.

let text = "Hello"; // متغير فيه نص   

console.log(text.startsWith("He"));
    // بيتأكد هل النص يبدأ بـ "He"
    // النتيجة: true
                    
console.log(text.startsWith("he"));
    // هنا الحروف مختلفة (H كبيرة)
    // JavaScript حساسة لحالة الحروف
    // النتيجة: false

💡 مفيدة جدًا في: التحقق من بداية إيميل التأكد من صيغة معينة فلترة بيانات

6️⃣ endsWith(): بتتحقق هل النص بينتهي بجزء معين.

let text = "Hello"; // متغير فيه نص
                    
console.log(text.endsWith("lo"));
    // بيتأكد هل النص بينتهي بـ "lo"
    // النتيجة: true
                    
console.log(text.endsWith("Lo"));
    // اختلاف في حالة الحروف
    // النتيجة: false

7️⃣ indexOf(): ترجع رقم مكان أول ظهور لقيمة داخل النص . لو مش موجودة → ترجع -1

let text = "Hello World"; // النص الأساسي
                    
console.log(text.indexOf("o"));
    // بيدور على أول حرف o
    // أول o موجود في المكان رقم 4
    // النتيجة: 4
                    
console.log(text.indexOf("z"));
    // الحرف z مش موجود
    // النتيجة: -1

8️⃣ lastIndexOf(): ترجع رقم مكان أخر ظهور لقيمة داخل النص لو مش موجودة → ترجع -1

let text = "Hello World";
                    
console.log(text.lastIndexOf("o"));
    // فيه o مرتين
    // آخر واحدة في المكان رقم 7
    // النتيجة: 7

9️⃣ slice(start, end): تستخدم لاستخراج جزء من النص.

let text = "JavaScript"; // النص الأساسي
                    
console.log(text.slice(0, 4));
    // يبدأ من index 0
    // يقف قبل index 4
    // النتيجة: "Java"
                    
console.log(text.slice(4));
    // لو حددنا بداية فقط
    // هياخد من 4 لآخر النص
    // النتيجة: "Script"

🔟 substring(): شبيهة بـ slice لكن لا تدعم الأرقام السالبة وأقل استخدامًا.

let text = "JavaScript";
                    
console.log(text.substring(0, 4));
    // نفس فكرة slice
    // النتيجة: "Java"

1️⃣1️⃣ replace() : تغير جزء من النص لكن تغير أول تطابق فقط
replaceAll() : تغير كل التطابقات.

let text = "Hello World World";
console.log(text.replace("World", "Ali"));
    // هيغير أول World بس
    // النتيجة: "Hello Ali World"

console.log(text.replaceAll("World", "Ali"));
    // هيغير كل الكلمات
    // النتيجة: "Hello Ali Ali"

1️⃣2️⃣ split(): تحويل النص إلى Array (مهمة جدًا).

let text = "apple,banana,orange";
    let result = text.split(","); // ["apple", "banana", "orange"]

1️⃣3️⃣ charAt(): ترجع حرف معين حسب مكانه.

let text = "Hello";
                    
console.log(text.charAt(0));
    // يرجع الحرف في المكان 0
    // النتيجة: H

1️⃣4️⃣ repeat(): تكرر النص عدد معين من المرات.

console.log("Hi ".repeat(3));
    // يكرر النص 3 مرات
    // النتيجة: "Hi Hi Hi "

🔹 أشياء مهمة جدًا عن Strings

1️⃣ Strings غير قابلة للتعديل (Immutable) : لا يمكن تعديل حرف داخل النص مباشرة. لازم تنشئ نص جديد.

let name = "Ali"; // النص الأصلي
                        
name[0] = "M"; // نحاول نغير أول حرف
                        
console.log(name);
    // النتيجة: Ali
    // لم يتغير لأنه Immutable

2️⃣ مقارنة النصوص: JavaScript حساسة لحالة الحروف.

console.log("Ali" === "Ali");
    // نفس النص → true
                        
console.log("ali" === "Ali");
    // اختلاف في حالة الحروف → false

3️⃣ تحويل رقم إلى String

let num = 100; // رقم
                        
let text = String(num); // تحويل إلى نص
                        
console.log(typeof text);
    // النتيجة: "string"

4️⃣ تحويل String إلى رقم

let text = "100"; // نص
                        
let num = Number(text); // تحويل إلى رقم
                        
console.log(typeof num);
    // النتيجة: "number"

❌ أخطاء شائعة (تجنبها!)

  • 1. نسيان trim() عند استقبال بيانات من المستخدم.
  • 2. نسيان إن الـ index يبدأ من 0.
  • 3. استخدام replace وتوقع إنه يغير كل الكلمات (استخدم replaceAll).
  • 4. مقارنة نصوص بدون توحيد الحروف باستخدام toLowerCase().

🏆 تحدي الـ String

function formatUserData(input) {
                
  // 1️⃣ إزالة المسافات الزائدة من البداية والنهاية
  let cleanedInput = input.trim();
                
  // 2️⃣ تقسيم النص باستخدام الفاصلة
  let parts = cleanedInput.split(",");
                
  // إزالة المسافات من كل عنصر بعد التقسيم
  let name = parts[0].trim();
  let age = parts[1].trim();
  let job = parts[2].trim();
                
  // 3️⃣ التحقق إن العمر رقم
  let numericAge = Number(age);
                
  if (isNaN(numericAge)) {
      return "Invalid age value";
      }
                
  // 4️⃣ التحقق من طول الاسم
  if (name.length < 3) { return "Name is too short" ; }
    
  // 5️⃣ تنسيق الاسم (أول حرف كابيتال والباقي small) let
  formattedName=name[0].toUpperCase() + name.slice(1).toLowerCase(); 
    
  // 6️⃣ تحويل الوظيفة إلى حروف كبيرة let
  formattedJob=job.toUpperCase(); 
    
  // 7️⃣ إرجاع النتيجة النهائية 
  return `User ${formattedName} is ${numericAge} years old and works as ${formattedJob}.`; }


console.log(formatUserData("  eslam, 21, frontend developer  "));

📦 الكائنات (Objects)

🔹 أولاً: الأساسيات (The Basics)

1. تعريف الـ Object وإنشائه

الـ Object في JavaScript هو هيكل بيانات يخزن البيانات في شكل أزواج (مفتاح: قيمة). يمكن إنشاؤه بطريقتين رئيسيتين: القول الحرفي (Literal) وهو الأكثر شيوعاً، أو باستخدام الكلمة new.

نستخدم الأقواس المعقوفة { } لإنشاء الكائن. بداخلها نكتب اسم الخاصية (Key) ثم نقطتين : ثم القيمة (Value)، ونفصل بين الخصائص بفاصلة ,.
// الطريقة الأولى: باستخدام Object Literal (الأكثر شيوعاً)
const person = {            // تعريف كائن اسمه person وتخزينه في ثابت const
    firstName: "Ahmed",       // تعريف خاصية firstName وقيمتها "Ahmed"
    lastName: "Mohamed",      // تعريف خاصية lastName وقيمتها "Mohamed"
    age: 25,                  // تعريف خاصية age وقيمتها الرقم 25
    isStudent: true           // تعريف خاصية isStudent وقيمتها true (منطقية)
};                          // إغلاق تعريف الكائن بفاصلة منقوطة
    
// الطريقة الثانية: باستخدام دالة البناء new Object()
const car = new Object();   // إنشاء كائن فارغ جديد باستخدام الكلمة المفتاحية new
car.brand = "Toyota";       // إضافة خاصية brand للكائن car وتعيين قيمتها
car.model = "Corolla";      // إضافة خاصية model للكائن car
car.year = 2022;            // إضافة خاصية year للكائن car

2. الوصول للخصائص (Accessing Properties)

للوصول لقيمة خاصية معينة، لدينا طريقتان: النقطة . والأقواس المربعة [ ].

طريقة النقطة تستخدم عندما نعرف اسم الخاصية مسبقاً. طريقة الأقواس المربعة تستخدم عندما يكون اسم الخاصية مخزناً في متغير، أو عندما يحتوي الاسم على مسافات أو رموز خاصة.
const user = {             // تعريف كائن user
      name: "Sara",            // خاصية name
      "user age": 30           // خاصية تحتوي على مسافة (يجب استخدام علامات تنصيص)
    };                       
    
// الوصول باستخدام Dot Notation (النقطة)
console.log(user.name);    // طباعة قيمة الخاصية name (النتيجة: Sara)
    
// الوصول باستخدام Bracket Notation (الأقواس)
console.log(user["name"]); // طباعة قيمة الخاصية name أيضاً (النتيجة: Sara)
    
// حالة خاصة: لا يمكن استخدام النقطة مع المفاتيح التي تحتوي على مسافات
console.log(user["user age"]); // طباعة القيمة 30 (صحيح)
// console.log(user.user age); // هذا سيعطي خطأ (SyntaxError) لأن المسافة غير مسموحة في النقطة
    
// استخدام متغير لاسم المفتاح (Computed Property Name)
let key = "name";          // تعريف متغير يحمل اسم الخاصية
console.log(user[key]);    // طباعة القيمة باستخدام المتغير key (النتيجة: Sara)

3. التعديل والحذف (Modify & Delete)

الكائنات في JavaScript قابلة للتغيير (Mutable)، مما يعني أننا يمكننا تعديل قيمها، إضافة خصائص جديدة، أو حذفها في أي وقت.

لإضافة خاصية جديدة أو تعديل موجودة، نستخدم اسم الكائن ثم النقطة ثم الاسم الجديد والقيمة. للحذف، نستخدم الكلمة المحجوزة delete.
let book = {              // تعريف كائن book
      title: "JS Guide",      // خاصية العنوان
      pages: 200              // خاصية عدد الصفحات
    };
    
// تعديل خاصية موجودة
book.pages = 250;         // تغيير قيمة pages من 200 إلى 250
    
// إضافة خاصية جديدة
book.author = "John";     // إضافة خاصية author لم تكن موجودة من قبل
book.isAvailable = true;  // إضافة خاصية isAvailable
    
// حذف خاصية
delete book.isAvailable;  // حذف الخاصية isAvailable من الكائن تماماً
    
console.log(book);        // طباعة الكائن (لن نجد isAvailable)

🔹 ثانياً: الأساليب (Methods) وكلمة this

1. تعريف الـ Methods

الـ Method هي دالة (Function) يتم تخزينها كقيمة لأحد خصائص الكائن. تسمح للكائن بتنفيذ أفعال.

نكتب اسم الخاصية، ثم نتبعها بدالة عادية أو دالة سهمية (ولكن انتبه لـ this مع السهمية).
const calculator = {                // تعريف كائن آلة حاسبة
   add: function(a, b) {        // تعريف دالة (Method) اسمها add
        return a + b;             // إرجاع حاصل الجمع
      },
   subtract: (a, b) => a - b   // تعريف دالة سهمية (Arrow Function) اسمها subtract
  };
    
console.log(calculator.add(5, 3));      // استدعاء الدالة add وطباعة النتيجة (8)
console.log(calculator.subtract(10, 4)); // استدعاء الدالة subtract (6)

2. كلمة this

كلمة this تشير إلى الكائن الذي "يملك" الكود الذي يتم تنفيذه حالياً. داخل الـ Method، تشير this إلى الكائن نفسه.

تساعدنا this في الوصول إلى خصائص الكائن الأخرى من داخل الدوال الخاصة به.
const profile = {                     // تعريف كائن profile
    name: "Eslam",                // خاصية الاسم
    age: 21,                      // خاصية العمر
    introduce: function() {       // دالة للتعريف بالنفس
      // this.name هنا تشير إلى خاصية name الموجودة في هذا الكائن (profile)
      return "My name is " + this.name + " and I am " + this.age; 
    }
  };    
console.log(profile.introduce());   // طباعة: My name is Eslam and I am 21

🔹 ثالثاً: التعامل مع الكائنات وتعقيداتها (Manipulation)

1. النسخ بالمرجع (Copy by Reference)

عندما نسخ كائناً إلى متغير آخر، لا يتم نسخ البيانات، بل يتم نسخ "الإشارة" أو "العنوان" في الذاكرة.

أي تعديل يحدث على المتغير الجديد سينعكس على الأصلي لأنهما يشيران إلى نفس المكان في الذاكرة.
const obj1 = { a: 1 };   // تعريف كائن أصلي
const obj2 = obj1;       // نسخ المرجع (obj2 يشير لنفس مكان obj1)
    
obj2.a = 99;             // تعديل القيمة باستخدام obj2
    
console.log(obj1.a);     // طباعة obj1 سيعطي 99 (تأثر بالتعديل!)
console.log(obj2.a);     // طباعة obj2 سيعطي 99

2. النسخ السطحي (Shallow Copy)

لحل مشكلة المرجع، نستخدم طرقاً لإنشاء نسخة جديدة، لكن هذه الطرق (مثل Spread أو Object.assign) تنسخ فقط الطبقة الأولى.

الشرح النظري: إذا كان الكائن يحتوي على كائن آخر داخله (Nested Objects)، فالنسخة السطحية ستنسخ مرجع الكائن الداخلي وليس نسخة منه.
const original = {       // كائن أصلي
      name: "Test",          // خاصية بسيطة
      details: {             // كائن داخلي (Nested)
        id: 5
      }
    };
    
// النسخ باستخدام Spread Syntax (...)
const shallowCopy = { ...original }; // إنشاء كائن جديد ونسخ خصائص original فيه
    
shallowCopy.name = "Changed"; // تغيير خاصية بسيطة (لا يؤثر على الأصلي)
shallowCopy.details.id = 100; // تغيير خاصية داخل الكائن الداخلي (سيؤثر على الأصلي!)
    
console.log(original.name);    // الناتج: "Test" (لم يتغير)
console.log(original.details.id); // الناتج: 100 (تغير! لأن details لا يزال مشتركاً)

3. النسخ العميق (Deep Copy)

للحصول على استقلالية تامة، يجب عمل نسخة عميقة تنسخ الكائنات الداخلية أيضاً.

JSON.stringify يحول الكائن لنص، و JSON.parse يعيد النص لكائن جديد تماماً. (ملاحظة: هذه الطريقة لا تدعم الدوال أو undefined).
const original = {
      name: "Deep",
      data: { x: 10 }
  };
    
// تحويل الكائن إلى نص JSON ثم إعادة تحويله لكائن (نسخة مستقلة تماماً)
const deepCopy = JSON.parse(JSON.stringify(original));
    
deepCopy.data.x = 999; // تعديل الكائن الداخلي في النسخة العميقة
    
console.log(original.data.x); // الناتج: 10 (الأصلي لم يتأثر، هذا هو الهدف من النسخ العميق)

4. دمج الكائنات (Merging)

يمكننا دمج أكثر من كائن في كائن واحد.

const objA = { a: 1, b: 2 }; // الكائن الأول
const objB = { c: 3, b: 4 }; // الكائن الثاني (لاحظ تكرار المفتاح b)
    
// استخدام Spread لدمجهم (الكائن الأخير يطابق القيم المتكررة)
const merged = { ...objA, ...objB }; 
    
console.log(merged); // الناتج: { a: 1, b: 4, c: 3 } (قيمة b أخذت من objB)

🔹 رابعاً: ميزات حديثة ES6+ (Modern Object Features)

1. التفكيك (Destructuring)

تقنية لاستخراج قيم من الكائن ووضعها في متغيرات بسرعة.

بدلاً من كتابة const name = user.name; نكتب const { name } = user;
const user = { id: 1, username: "admin", role: "editor" };
                        
// استخراج username و role إلى متغيرات مستقلة
const { username, role } = user;
                        
console.log(username); // الناتج: "admin"
console.log(role); // الناتج: "editor"
// console.log(id); // خطأ لأننا لم نستخرج id

2. المفاتيح المحسوبة

إنشاء مفاتيح للكائن تعتمد على قيم متغيرات.

const keyName = "dynamicKey"; // متغير يحمل اسم المفتاح
const value = 100; // متغير يحمل القيمة
                        
const obj = { // تعريف كائن
    [keyName]: value // استخدام المفتاح المحسوب (سيكون اسم الخاصية dynamicKey)
 };
                        
console.log(obj.dynamicKey); // الناتج: 100

3. Optional Chaining (?.)

الوصول الآمن للخصائص لتجنب الأخطاء إذا كانت الخاصية null أو undefined.

إذا كان الجزء الأيسر من ?. غير موجود، يتوقف الكود ويعيد undefined بدلاً من إلقاء خطأ.
const user = { name: "Khaled" }; // كائن ليس لديه خاصية address
                
console.log(user.address.street); // خطأ! (Cannot read properties of undefined)
console.log(user.address?.street); // آمن: الناتج undefined (بدون خطأ)

4. Nullish Coalescing (??)

توفير قيمة افتراضية فقط إذا كانت القيمة null أو undefined (وليس 0 أو false).

const settings = { volume: 0 }; // الصفر قيمة صالحة
                
// || تعتبر الصفر قيمة خاطئة (falsy)
const v1 = settings.volume || 10; // الناتج: 10 (خطأ لأننا أردنا 0)
                
// ?? تعتبر فقط null و undefined قيم مفقودة
const v2 = settings.volume ?? 10; // الناتج: 0 (صحيح، لأن القيمة موجودة)

🔹 خامساً: الوراثة والبرمجة كائنية التوجه (Prototypes & OOP)

1. سلسلة النموذج الأولي (Prototype Chain)

كل كائن في JavaScript له خاصية مخفية تسمى prototype ترث منها الخصائص.

إذا طلبت خاصية غير موجودة في الكائن، سيبحث JavaScript في prototype الخاص به، ثم prototype الكائن الأب، وهكذا.
const animal = { eats: true }; // كائن أب
                
const rabbit = { jumps: true }; // كائن ابن
                
// ربط rabbit بـ animal كـ prototype
rabbit.__proto__ = animal; // (طريقة قديمة للشرح فقط)
                
console.log(rabbit.eats); // الناتج: true (ورثها من animal)
console.log(rabbit.jumps); // الناتج: true (خاصية الأصل)

2. المُنشئات (Constructor Functions)

دوال تُستخدم لإنشاء كائنات متشابهة باستخدام الكلمة new.

function User(name) { // تعريف دالة منشئ (نكتب أول حرف كبير عادة)
    this.name = name; // this هنا يشير للكائن الذي سيُنشأ
    this.isAdmin = false; // إضافة خاصية افتراضية
 }
                
const user1 = new User("Omar"); // إنشاء كائن جديد
console.log(user1.name); // الناتج: "Omar"
console.log(user1.isAdmin); // الناتج: false

3. الـ Classes (ES6 Classes)

طريقة حديثة وأكثر تنظيماً للكتابة البرمجية كائنية التوجه (OOP)، لكنها في الأساس "سكر نحوي" للـ Prototypes.

تحتوي الـ Class على دالة بناء خاصة constructor، ودوال للكائن (Methods).
class Vehicle { // تعريف كلاس (قالب) اسمه Vehicle
    constructor(brand) { // دالة البناء تُنفذ عند إنشاء كائن جديد
    this.brand = brand; // تخزين العلامة التجارية في الكائن
     }
                
    showBrand() { // دالة (Method) داخل الكلاس
    console.log("Brand: " + this.brand);
        }
}
                
const car = new Vehicle("BMW"); // إنشاء كائن (instance) من الكلاس
car.showBrand(); // استدعاء الدالة (النتيجة: Brand: BMW)

4. الوراثة في الكلاس (Inheritance)

استخدام extends لإنشاء كلاس جديد يرث خصائص كلاس قديم، و super لاستدعاء الكلاس الأب.

class Car extends Vehicle { // الكلاس Car يرث من Vehicle
    constructor(brand, model) {
        super(brand); // استدعاء constructor الأب لإرسال brand
        this.model = model; // إضافة خاصية خاصة بالابن
            }
                
    showInfo() {
        console.log(this.brand + " " + this.model);
            }
    }
                
const myCar = new Car("Mercedes", "C200"); // إنشاء كائن من الابن
myCar.showInfo(); // الناتج: Mercedes C200

🔹 سادساً: خصائص متقدمة (Advanced Properties)

1. Getters و Setters

دوال تسمح لنا بالتحكم في قراءة وتعديل الخصائص (كأنها خصائص عادية).

get تُستخدم عند قراءة القيمة، set تُستخدم عند تعيين قيمة جديدة. مفيدة للتحقق (Validation).
const userObj = {
    firstName: "Ali",
    lastName: "Hassan",
                
    // Getter: دالة تُنفذ عند طلب fullName
    get fullName() {
        return this.firstName + " " + this.lastName;
            },
                
    // Setter: دالة تُنفذ عند تعيين قيمة لـ fullName
    set fullName(value) {
        const parts = value.split(" "); // تقسيم النص
        this.firstName = parts[0]; // تحديث الاسم الأول
        this.lastName = parts[1]; // تحديث اسم العائلة
            }
        };
                
console.log(userObj.fullName); // تنفيذ get (النتيجة: Ali Hassan)
                
userObj.fullName = "Sara Ahmed"; // تنفيذ set وتحديث البيانات
console.log(userObj.firstName); // النتيجة: Sara

2. Property Descriptors & 3. Immutability

Property Descriptors

التحكم الدقيق في سلوك الخاصية (قابلة للكتابة؟، تظهر في الحلقات؟، قابلة للحذف؟).

const descriptorObj = { name: "Test" }; // كائن عادي
                        
// منع كتابة (تعديل) الخاصية
Object.defineProperty(descriptorObj, "name", {
    writable: false, // جعل الخاصية للقراءة فقط
    configurable: false // منع حذف الخاصية
});
                        
descriptorObj.name = "New Name"; // لن ينجح في الوضع الصارم
console.log(descriptorObj.name); // النتيجة: Test

تجميد الكائنات

منع تغيير الكائن نهائياً أو جزئياً.

const immutableObj = { a: 1, b: 2 };
                        
// Freeze: يجمد الكائن (لا إضافة، لا حذف، لا تعديل)
Object.freeze(immutableObj);
                        
immutableObj.a = 99; // لن يحدث شيء (صامت في الوضع العادي)
immutableObj.c = 3; // لن يتم الإضافة
                        console.log(immutableObj); // { a: 1, b: 2 }
                        
// Seal: يغلق الكائن (لا إضافة، لا حذف)، لكن يسمح بتعديل القيم الموجودة
const sealedObj = { x: 10 };
Object.seal(sealedObj);
sealedObj.x = 20; // مسموح (تعديل)
// sealedObj.y = 5; // ممنوع (إضافة)

🔹 سابعاً: التكرار والتطبيقات (Iteration & Utilities)

1. التكرار باستخدام for...in

للمرور على مفاتيح الكائن (Keys).

const data = { a: 10, b: 20, c: 30 };
                
for (let key in data) { // حلقة تمر على كل مفتاح
    console.log(key); // طباعة المفتاح (a, b, c)
    console.log(data[key]); // طباعة القيمة باستخدام المفتاح
}

2. دوال التحول (Transformation)

توفر JavaScript طرقاً سريعة لاستخراج البيانات من الكائن وتحويلها إلى مصفوفات (Arrays) ليسهل التعامل معها:

🔑 Object.keys()

إرجاع مصفوفة تحتوي على جميع **المفاتيح** (Keys) الموجودة في الكائن.

Object.keys(user); // ["id", "name"]
💎 Object.values()

إرجاع مصفوفة تحتوي على جميع **القيم** (Values) الموجودة في الكائن.

Object.values(user); // [1, "Ahmed"]
🔗 Object.entries()

إرجاع مصفوفة من الأزواج، كل زوج عبارة عن مصفوفة صغيرة [key, value].

Object.entries(user);
// [["id", 1], ["name", "Ahmed"]]
const session = { id: 1, user: "guest" };
                
const keys = Object.keys(session); // ['id', 'user']
const values = Object.values(session); // [1, 'guest']
const entries = Object.entries(session); // [['id', 1], ['user', 'guest']]
                
console.log(keys); // طباعة المفاتيح

🔹 ثامناً: التعامل مع البيانات (JSON)

1. التسلسل وإلغاء التسلسل (Serialization & Deserialization)

نستخدم JSON (JavaScript Object Notation) كصيغة عالمية لتحويل الكائنات إلى نصوص لإرسالها عبر الإنترنت إلى الخوادم (Servers)، ثم إعادتها لأصلها عند الاستلام.

Object
{ id: 55 }
JSON.stringify()
JSON.parse()
JSON String
'{id":55"}'
const myData = { id: 55, title: "Product A" };
        
// 1. Serialization: تحويل الـ Object إلى نص JSON
const jsonText = JSON.stringify(myData); 
console.log(jsonText); 
// الناتج: '{"id":55,"title":"Product A"}' (نص جاهز للإرسال)
        
// 2. Deserialization: تحويل نص JSON إلى Object مرة أخرى
const backToObject = JSON.parse(jsonText);
console.log(backToObject.title); 
// الناتج: "Product A" (كائن يمكن التعامل معه برمجياً)

🔹 إضافات

1. الحقول والأساليب الخاصة (Private Fields & Methods #)

في عام 2022، أضافت JavaScript طريقة رسمية لعمل خصائص "خاصة" (Private) لا يمكن الوصول إليها من خارج الكائن، باستخدام الرمز # قبل اسم الخاصية.

أي خاصية تبدأ بـ # تُعتبر خاصة تماماً ومحمية، ولا يمكن قراءتها أو تعديلها إلا من داخل الكلاس نفسه.
class BankAccount {
    #balance = 0; // خاصية خاصة (Private Field) - لا يمكن الوصول لها من خارج الكلاس
        
    constructor(owner) {
      this.owner = owner; // خاصية عامة
     }
        
    deposit(amount) {
      this.#balance += amount; // مسموح الوصول لها هنا لأننا داخل الكلاس
      console.log(`تم الإيداع، الرصيد الحالي: ${this.#balance}`);
     }
        
    showBalance() {
      return this.#balance; // دالة خاصة لتقديم الرصيد للخارج بشكل آمن
     }
  }
        
const myAccount = new BankAccount("Ali");
myAccount.deposit(500);
        
// console.log(myAccount.#balance); // هذا سيعطي خطأ (SyntaxError) لأن الخاصية خاصة
console.log(myAccount.showBalance()); // الطريقة الصحيحة للوصول (500)

2. كتابة الدوال المختصرة داخل الكائنات (Method Shorthand)

في ES6، يمكننا كتابة الدوال داخل الكائنات (ليس الكلاسات) بطريقة مختصرة دون استخدام الكلمة function.

بدلاً من sayHello: function() {}، نكتب sayHello() {}. هذا يجعل الكود أنظف.
const user = {
    name: "Mona",
          
    // الطريقة القديمة (Old Syntax)
    greetOld: function() {
        return "Hello";
    },
        
    // الطريقة المختصرة الحديثة (Method Shorthand)
    greetNew() {         // لا نحتاج لكتابة : function
        return "Hi, " + this.name;
          }
 };
        
console.log(user.greetNew()); // الناتج: Hi, Mona

3. معامل in للتحقق من وجود الخاصية

لدينا طريقتان للتحقق مما إذا كانت خاصية موجودة: hasOwnProperty و in.

💡 الفرق الجوهري:
hasOwnProperty لا يتحقق في الـ Prototype (الوراثة).
in يتحقق في الكائن الحالي وفي سلسلة الوراثة بالكامل.
📝 التعيير key in obj يعود بـ true إذا وجد المفتاح في الكائن نفسه أو ورثه من أب.
const parent = { inheritedProp: 100 };
const child = Object.create(parent); // child يرث من parent
child.ownProp = 200;                 // خاصية خاصة بالابن
        
console.log("inheritedProp" in child); // true (وجدت في الـ prototype)
console.log("ownProp" in child);      // true (وجدت في الكائن نفسه)
        
console.log(child.hasOwnProperty("inheritedProp")); // false (ليست خاصة به)
console.log(child.hasOwnProperty("ownProp"));      // true (خاصة به)

4. مقارنة الكائنات (Object Comparison) & Object.is()

في JavaScript، الكائنات تُقارن بالمرجع (Reference) وليس بالقيمة (Value).

أيضاً، هناك دالة Object.is() تختلف قليلاً عن معامل المقارنة الصارم ===.

🧠 الشرح النظري والمقارنة:
  • obj1 === obj2 تكون true فقط إذا كانا يشيران لنفس المكان في الذاكرة.
  • Object.is(v1, v2) تشبه === لكنها تعتبر أن NaN يساوي نفسه.
const a = { x: 1 };
const b = { x: 1 };
        
// 1. المقارنة بالمرجع
console.log(a === b); // false (هما كائنان مختلفان في الذاكرة رغم تشابه المحتوى)
        
// 2. Object.is (للمتغيرات العادية والقيم الخاصة)
console.log(NaN === NaN);       // false (غريب لكن صحيح في JS)
console.log(Object.is(NaN, NaN)); // true (تعالج هذه الحالة)
console.log(Object.is(-0, 0));  // false (تفرق بين الصفر الموجب والسالب)

5. معامل instanceof

يُستخدم للتحقق مما إذا كان كائن معين قد تم إنشاؤه بواسطة كلاس (Class) معين أو دالة بناء (Constructor) معينة.

يفحص سلسلة الـ Prototype ليرى ما إذا كان prototype للدالة البناء موجوداً في سلسلة وراثة الكائن.
class Animal { }
class Dog extends Animal { }
        
const myDog = new Dog();
        
console.log(myDog instanceof Dog);    // true (myDog كائن من نوع Dog)
console.log(myDog instanceof Animal); // true (Dog يرث من Animal)
console.log(myDog instanceof Object); // true (كل شيء يرث من Object في النهاية)

6. الـ Symbols كـ مفاتيح للكائنات

Symbol هو نوع بيانات بدائي في JS يمكن استخدامه كمفتاح للكائن. مميزته أنه فريد (Unique) ومخفي (غير مرئي) في بعض عمليات التكرار.

إذا أردت إضافة خاصية لكائن دون أن تظهر في for...in أو Object.keys، استخدم Symbol
const id = Symbol("id"); // إنشاء رمز فريد
        
const user = {
    name: "Hassan",
    [id]: 12345 // استخدام الرمز كمفتاح (بين أقواس معقوفة)
  };
        
console.log(user[id]); // 12345 (يمكن الوصول له فقط إذا كان لديك الرمز)
console.log(Object.keys(user)); // ["name"] (المفتاح الرمزي مخفي)

7. الفرق بين Object.assign و Spread Operator (...) في التعديل

استخدمنا الـ Spread (...) للدمج والنسخ، ولكن Object.assign له سلوك مختلف فيما يخص
التعديل (Mutation).

⚖️ الفرق في التعامل مع الذاكرة:

{...obj} يُنشئ دائماً كائناً جديداً (Immutable approach).

Object.assign(target, source) يعدل الكائن الأول target مباشرة ويرجعه.

const target = { a: 1 };
const source = { b: 2 };
        
// Spread: ينشئ كائناً جديداً ولا يلم الأصلي
const newObj = { ...target, ...source }; 
        
// Object.assign: يدمج المصدر في الهدف ويعدل الهدف مباشرة
const assignedObj = Object.assign(target, source); 
        
console.log(target); // { a: 1, b: 2 } (تغير لأنه كان الهدف!)
console.log(newObj); // { a: 1, b: 2 } (كائن جديد مستقل)

8. Static Members (الأعضاء الساكنة) في الكلاس

هي خصائص أو دوال تُستدعى من "الكلاس نفسه" وليس من "الكائن (Instance)".

نستخدمها عندما نريد دالة أو خاصية مشتركة بين جميع الكائنات ولا تعتمد على بيانات كائن محدد (مثل عداد لعدد الكائنات المنشأة).
class User {
    static count = 0; // خاصية ساكنة مشتركة
        
    constructor(name) {
        this.name = name;
        User.count++;   // زيادة العداد عند إنشاء كائن جديد
        }
        
    static getCount() { // دالة ساكنة
        return User.count;
        }
    }
        
const u1 = new User("Ali");
const u2 = new User("Sara");
        
// console.log(u1.count); // undefined (لا يمكن الوصول لها من الكائن)
console.log(User.getCount()); // 2 (نستدعيها من اسم الكلاس مباشرة)

🏆 تحدي الكائنات (JSON Challenge)

لدينا كائن: const user = { id: 10 };
اكتب كود لتحويله إلى نص JSON وتخزينه في متغير اسمه text.