المرحلة 0 · الأسبوع 4 · الدرس 0013 · ~40 دقيقة

Prototype Chain — السلسلة المختفية ورا كل أوبجكت

ليه الدرس ده؟ في درس 0008 قلنا إن new Person("سارة") بتعمل 4 حاجات، وواحدة منهم كانت "بتربطه بـ Person.prototype (تفاصيلها أسبوع 4)" — النهاردة بنفتح الجملة المؤجلة دي، ونشوف كمان ليه استخدام الـ prototype أوفر في الذاكرة من الـ closures اللي بنيت بيها كل حاجة في أسبوع 3.

🔁 مراجعة سريعة قبل ما تبدأ (~5 دقايق) — بالأمثلة:

من 0008 — الـ 4 خطوات اللي new بتعملها:

function Person(name) {
  this.name = name;
}
const p = new Person("سارة");

افتكر: الخطوة التانية من الأربعة كانت "بتربط الأوبجكت الجديد بـ Person.prototype" — النهاردة هنشوف ده معناه إيه بالظبط.

من 0010/0012 — الطريقة اللي بنيت بيها methods لحد دلوقتي (factory + closure):

function createPerson(name) {
  return {
    name,
    greet() { return "أهلاً " + this.name; }
  };
}
const a = createPerson("سارة");
const b = createPerson("عمر");
a.greet === b.greet;

توقع من الذاكرة: a.greet === b.greet بترجع true ولا false؟ (تلميح: كل نداء لـ createPerson بيعمل closure مستقل — يبقى كل نسخة بتاخد دالة greet جديدة خاصة بيها في الذاكرة.)

📖 لو سألتك: "إيه هو الـ Prototype؟"

الـ Prototype هو object تاني كل object في JS مرتبط بيه عن طريق رابط داخلي (يتقرا بـ Object.getPrototypeOf()) — مش property عادية بتظهر في الـ object نفسه. ولما تحاول توصل لـ property أو method مش موجودة على الـ object مباشرة، الـ engine بيدوّر عليها في الـ prototype بتاعه، وبعدين في prototype الـ prototype، وهكذا لحد ما يوصل لـ null. السلسلة دي اسمها Prototype Chain.

١ — الفكرة في 3 جمل

① كل object في JS مرتبط بـ Prototype — object تاني بيشاور عليه رابط داخلي، مش property عادية بتتلف مع for...in أو Object.keys().

② لما تطلب property/method مش موجودة مباشرة على الـ object، الـ engine بيدوّر تلقائيًا في السلسلة دي — بالظبط زي سلسلة البحث بتاعة الـ scope (0003)، بس هنا للـ objects مش للمتغيرات.

③ الـ constructor functions زي Person ليها property اسمها .prototype، وأي object اتعمل بـ new Person() بيتربط بيه تلقائيًا — دي بالظبط الخطوة التانية من 0008.

٢ — مثال أساسي: method واحدة، كل الـ instances بتشاركها

function Person(name) {
  this.name = name;
}
Person.prototype.greet = function () {
  return "أهلاً " + this.name;
};

const p1 = new Person("سارة");
const p2 = new Person("عمر");

console.log(p1.greet());
console.log(p2.greet());
console.log(p1.greet === p2.greet);

الناتج الحقيقي سطر سطر:

أهلاً سارة
أهلاً عمر
true

✓ متحقق بتشغيل Node.js فعلي (2026-08-26). لاحظ الفرق عن مثال المراجعة فوق: هناك a.greet === b.greet كانت false (كل closure بيبني دالة جديدة)، وهنا true — دالة واحدة بس في الذاكرة، كل الـ instances بتشاور عليها عن طريق الـ prototype. ده الفرق العملي الأكبر بين الطريقتين: الـ closures بتاخد ذاكرة أكتر (نسخة لكل instance)، والـ prototype بتاخد ذاكرة أقل (نسخة واحدة مشتركة) — لكن بتفقد الخصوصية الكاملة (private state) اللي الـ closures بتديهالك.

٣ — Own property ضد Inherited property

الاسم بتاع الـ instance (name) اتحط مباشرة على الـ object نفسه جوه الـ constructor (this.name = name)، أما greet فموجودة بس على الـ Person.prototype — مش على كل instance. تقدر تتأكد بـ hasOwnProperty:

console.log(p1.hasOwnProperty("name"));
console.log(p1.hasOwnProperty("greet"));
true
false

✓ متحقق بتشغيل Node.js فعلي (2026-08-26). greet بترجع false لـ hasOwnProperty رغم إن p1.greet() شغالة عادي — لأن الاستدعاء بيلاقيها في السلسلة (على Person.prototype)، مش على p1 نفسه.

٤ — نهاية السلسلة: Object.prototype ثم null

السلسلة مش بتقف عند Person.prototype — هي نفسها ليها prototype، لحد ما توصل لآخر حلقة. جرّب method مش معرّفها إنت خالص:

console.log(p1.toString());
console.log(Object.getPrototypeOf(Person.prototype) === Object.prototype);
console.log(Object.getPrototypeOf(Object.prototype));
[object Object]
true
null

✓ متحقق بتشغيل Node.js فعلي (2026-08-26). toString مش موجودة لا على p1 ولا على Person.prototype — السلسلة كمّلت لحد Object.prototype (اللي كل الـ objects العادية بترثه تلقائيًا) ولقتها هناك. وObject.prototype نفسها الـ prototype بتاعها null — دي نهاية أي سلسلة prototype في JS.

٥ — شوفها بعينك: رحلة البحث في السلسلة

لاحظ الشبه بمحاكي سلسلة البحث بتاع الـ scope (0003) — نفس فكرة "دوّر جوه، ولو مش لاقي اطلع لبره لحد ما تلاقي أو توصل النهاية"، بس هنا للـ objects. توقع قبل كل خطوة: هيلاقيها فين؟

function Person(name) {
  this.name = name;
}
Person.prototype.greet = function () {
  return "أهلاً " + this.name;
};
const p1 = new Person("سارة");
p1.name;
p1.greet();
p1.toString();
دوس «خطوة» عشان نبدأ.
Console:
🔗 Prototype Chain

٦ — الربط بالواقع: فين ده مستخدم فعليًا؟

الاستخدام الحالي — مش هتكتبه بإيدك، بس هتستخدمه كل يوم: في كود 2020+ الحقيقي، محدش بيكتب Constructor.prototype.method = ... بإيده كده — الكل بيستخدم class (الدرس الجاي إن شاء الله) لأنها composer نضيف فوق نفس الآلية بالظبط. جرّب بنفسك:

class Person {
  constructor(name) { this.name = name; }
  greet() { return "أهلاً " + this.name; }
}
const p = new Person("سارة");
console.log(Object.prototype.hasOwnProperty.call(Person.prototype, "greet"));
console.log(p.hasOwnProperty("greet"));
true
false

✓ متحقق بتشغيل Node.js فعلي (2026-08-26). لاحظ الناتج: مطابق تمامًا لمثال قسم ٣ فوق (اللي كتبناه بـ function + .prototype بإيدنا). class مش آلية جديدة — هي واجهة أنضف (syntactic sugar) فوق نفس الـ prototype chain اللي شفناها النهاردة. عشان كده فهم الدرس ده شرط أساسي قبل ما تفهم الـ class فهم حقيقي، مش سطحي.

أماكن تانية هتشوف فيها المبدأ ده شغال ورا الكواليس:

٧ — توقع قبل ما تشوف (3 تمارين)

function Car(model) { this.model = model; }
Car.prototype.honk = function () { return "بيب!"; };
const c1 = new Car("لادا");
const c2 = new Car("تويوتا");
console.log(c1.honk === c2.honk);
function Car(model) { this.model = model; }
Car.prototype.honk = function () { return "بيب!"; };
const c1 = new Car("لادا");
console.log(c1.hasOwnProperty("honk"));
console.log(Object.getPrototypeOf(Object.prototype));

٨ — اختبر نفسك

س1: التعريف الدقيق لـ Prototype Chain؟

س2: ليه p1.greet === p2.greet بترجع true، بينما نفس الفكرة بالـ closures (0010) كانت بترجع false؟

س3: p1.hasOwnProperty("greet") بترجع false رغم إن p1.greet() شغالة، ده معناه إيه؟

س4 (سؤال judgment): امتى تختار prototype method بدل factory + closure؟

٩ — التحدي: صمّم constructor بـ shared method (من غير AI!)

المطلوب: constructor function اسمها Book(title, author)، وحط عليها method واحدة على الـ prototype اسمها describe() بترجع جملة فيها العنوان والمؤلف.

اعمل نسختين (book1, book2) بعناوين مختلفة، ونادي describe() من الاتنين وتأكد إن النتيجة صح.

🎁 بونص: أثبت بالكود إن book1.describe === book2.describe، وإن book1.hasOwnProperty("describe") بترجع false.

١٠ — المصادر الأساسية للتعمق