مقدمة
النماذج الأولية هي الآلية التي ترث بها كائنات جافا سكريبت خصائصها من بعضها البعض. في هذه المقالة، نشرح ماهية النموذج الأولي، وكيفية عمل سلاسل النماذج الأولية، وكيفية تعيين نموذج أولي لكائن ما.
المتطلبات الأساسية
فهم وظائف جافا سكريبت، والتعرف على أساسيات جافا سكريبت (انظر قسم البدء وقسم بناء المكونات)، ومبادئ OOJS (انظر قسم مقدمة إلى الكائنات).
سلسلة النماذج الأولية
في وحدة تحكم المتصفح، حاول إنشاء كائن حرفي:
const myObject = {
city: "Madrid",
greet() {
console.log(`Greetings from ${this.city}`);
},
};
myObject.greet(); // Greetings from Madridهذا كائن يحتوي على خاصية بيانات واحدة، وهي "city"، ودالة واحدة، وهي "greet()". إذا كتبت اسم الكائن متبوعًا بنقطة في وحدة التحكم، مثل "myObject."، فستعرض وحدة التحكم قائمة بجميع الخصائص المتاحة لهذا الكائن. ستلاحظ وجود العديد من الخصائص الأخرى بالإضافة إلى "city" و"greet()"!
__defineGetter__
__defineSetter__
__lookupGetter__
__lookupSetter__
__proto__
city
constructor
greet
hasOwnProperty
isPrototypeOf
propertyIsEnumerable
toLocaleString
toString
valueOfحاول الوصول إلى أحدها:
myObject.toString(); // "[object Object]"
إنها تعمل (حتى لو لم يكن واضحًا ما تفعله الدالة toString()).
ما هي هذه الخصائص الإضافية ومن أين تأتي؟
يحتوي كل كائن في جافا سكريبت على خاصية مُدمجة تُسمى النموذج الأولي (prototype). النموذج الأولي هو كائن بحد ذاته، لذا سيكون لكل نموذج أولي نموذج أولي خاص به، مما يُنشئ ما يُسمى بسلسلة النماذج الأولية. تنتهي هذه السلسلة عندما نصل إلى نموذج أولي تكون قيمته فارغة (null).
ملاحظة: لا يُطلق على خاصية الكائن التي تُشير إلى نموذجه الأولي اسم "نموذج أولي". هذا الاسم ليس قياسيًا، ولكن عمليًا، تستخدم جميع المتصفحات __proto__. الطريقة القياسية للوصول إلى النموذج الأولي للكائن هي استخدام الدالة Object.getPrototypeOf().
عند محاولة الوصول إلى خاصية من خصائص كائن ما: إذا لم يتم العثور على الخاصية في الكائن نفسه، يتم البحث في النموذج الأولي للخاصية. إذا لم يتم العثور على الخاصية بعد ذلك، يتم البحث في نموذج الكائن، وهكذا حتى يتم العثور على الخاصية أو الوصول إلى نهاية سلسلة البحث، وفي هذه الحالة يتم إرجاع قيمة غير مُعرَّفة.
لذا عندما نستدعي الدالة myObject.toString()، فإن المتصفح:
- يبحث عن toString في myObject
- لا أستطيع العثور عليه هناك، لذا يبحث في النموذج الأولي للكائن myObject عن toString
- يجده هناك ويتصل به.
ما هو النموذج الأولي للكائن myObject؟ لمعرفة ذلك، يمكننا استخدام الدالة Object.getPrototypeOf():
Object.getPrototypeOf(myObject); // Object { }
هذا كائن يُسمى Object.prototype، وهو النموذج الأولي الأساسي الذي تمتلكه جميع الكائنات افتراضيًا. النموذج الأولي Object.prototype فارغ (null)، لذا فهو يقع في نهاية سلسلة النماذج الأولية.
ليس النموذج الأولي للكائن دائمًا هو Object.prototype. جرب هذا:
const myDate = new Date();
let object = myDate;
do {
object = Object.getPrototypeOf(object);
console.log(object);
} while (object);
// Date.prototype
// Object { }
// nullيقوم هذا الكود بإنشاء كائن من نوع Date، ثم ينتقل إلى أعلى سلسلة النماذج الأولية ويسجلها. يوضح لنا أن النموذج الأولي لـ myDate هو كائن Date.prototype، وأن نموذجه الأولي هو Object.prototype.
في الواقع، عندما تستدعي طرقًا مألوفة، مثل myDate2.getTime()، فإنك تستدعي طريقة معرفة في Date.prototype.
عقارات شادين
ماذا يحدث إذا قمت بتعريف خاصية في كائن ما، بينما توجد خاصية أخرى تحمل نفس الاسم في النموذج الأولي للكائن؟ دعني أرى:
const myDate = new Date(1995, 11, 17);
console.log(myDate.getTime()); // 819129600000
myDate.getTime = function () {
console.log("something else!");
};
myDate.getTime(); // 'something else!'هذا متوقع بالنظر إلى وصف سلسلة النماذج الأولية. عند استدعاء الدالة getTime()، يبحث المتصفح أولاً في المتغير myDate عن خاصية بهذا الاسم، ولا يتحقق من النموذج الأولي إلا إذا لم تكن هذه الخاصية مُعرّفة في myDate. لذا، عند إضافة getTime() إلى myDate، يتم استدعاء النسخة الموجودة في myDate.
يُطلق على هذا اسم "مراقبة" العقار.
إعداد نموذج أولي
هناك عدة طرق لتعيين النموذج الأولي للكائن في جافا سكريبت، وسنصف هنا اثنتين: Object.create() والمنشئ.
باستخدام Object.create
تقوم طريقة Object.create() بإنشاء كائن جديد وتسمح لك بتحديد كائن لاستخدامه كنموذج أولي للكائن الجديد.
وهنا مثال:
const personPrototype = {
greet() {
console.log("hello!");
},
};
const carl = Object.create(personPrototype);
carl.greet(); // hello!هنا نقوم بإنشاء كائن من نوع personPrototype يحتوي على دالة greet(). ثم نستخدم Object.create() لإنشاء كائن جديد باستخدام personPrototype كنموذج أولي له. الآن يمكننا استدعاء greet() على الكائن الجديد، وسيوفر النموذج الأولي تنفيذه.
استخدام المنشئ
في لغة جافا سكريبت، تحتوي جميع الدوال على خاصية تسمى prototype. عند استدعاء دالة كدالة إنشاء، يتم تعيين هذه الخاصية إلى النموذج الأولي للكائن الذي تم إنشاؤه حديثًا (بحسب الاصطلاح، في خاصية تسمى __proto__).
لذا، إذا قمنا بتحديد النموذج الأولي للمنشئ، فيمكننا ضمان أن جميع الكائنات التي تم إنشاؤها باستخدام هذا المنشئ يتم إعطاؤها هذا النموذج الأولي:
const personPrototype = {
greet() {
console.log(`hello, my name is ${this.name}!`);
},
};
function Person(name) {
this.name = name;
}
Object.assign(Person.prototype, personPrototype);
// or
// Person.prototype.greet = personPrototype.greet;هنا نبدع:
- كائن من نوع personPrototype يحتوي على دالة greet()
- دالة إنشاء Person() تقوم بتهيئة اسم الشخص المراد إنشاؤه.
ثم نضع الطرق المحددة في personPrototype في خاصية النموذج الأولي لوظيفة Person باستخدام Object.assign.
بعد هذا الكود، ستتلقى الكائنات التي تم إنشاؤها باستخدام Person.prototype() كنموذج أولي لها، والذي يحتوي تلقائيًا على طريقة greet.
const reuben = new Person("Reuben");
reuben.greet(); // hello, my name is Reuben!وهذا يفسر أيضًا سبب قولنا سابقًا أن النموذج الأولي لـ myDate يسمى Date.prototype: هذه هي خاصية النموذج الأولي لمنشئ التاريخ.
امتلاك عقار
تحتوي الكائنات التي ننشئها باستخدام مُنشئ Person أعلاه على خاصيتين:
- خاصية الاسم، يتم تعيينها في المُنشئ، بحيث تظهر مباشرةً على كائنات الشخص
- تم تعيين دالة greet() في النموذج الأولي.
من الشائع رؤية هذا النمط، حيث تُعرَّف الدوال في النموذج الأولي، بينما تُعرَّف خصائص البيانات في الدالة البانية. والسبب هو أن الدوال عادةً ما تكون متطابقة لكل كائن نُنشئه، بينما نرغب غالبًا في أن يكون لكل كائن قيمة فريدة خاصة به لخصائص بياناته (كما هو الحال هنا، حيث لكل شخص اسم مختلف).
تُسمى الخصائص التي يتم تعريفها مباشرة على الكائن، مثل الاسم هنا، بالخصائص الخاصة، ويمكنك التحقق مما إذا كانت الخاصية خاصية معينة باستخدام طريقة Object.hasOwn() الثابتة:
const irma = new Person("Irma");
console.log(Object.hasOwn(irma, "name")); // true
console.log(Object.hasOwn(irma, "greet")); // falseملاحظة: يمكنك أيضًا استخدام طريقة Object.hasOwnProperty() غير الثابتة هنا، ولكننا نوصي باستخدام Object.hasOwn() إن أمكن.
النماذج الأولية والوراثة
تُعد النماذج الأولية ميزة قوية ومرنة للغاية في لغة جافا سكريبت تسمح بإعادة استخدام التعليمات البرمجية وتكوين الكائنات.
وهي تدعم تحديدًا نوعًا من الوراثة. الوراثة هي إحدى خصائص لغات البرمجة كائنية التوجه، والتي تسمح للمبرمجين بالتعبير عن فكرة أن بعض الكائنات في النظام هي نسخ أكثر تخصصًا من كائنات أخرى.
على سبيل المثال، إذا كنا نصمم نموذجًا لمدرسة، فقد يكون لدينا معلمين وطلاب: كلاهما أشخاص، لذا يشتركان في بعض الخصائص (مثلًا، لكل منهما اسم)، ولكن قد يضيف كل منهما خصائص إضافية (مثلًا، للمعلمين مادة يدرسونها)، أو قد يطبقان الخاصية نفسها بطرق مختلفة. في نظام البرمجة الكائنية، يمكننا القول إن المعلمين والطلاب كلاهما يرثان من فئة الأشخاص (People).
يمكنك أن ترى كيف أنه في جافا سكريبت، إذا كان بإمكان كائنات الأستاذ والطالب أن تحتوي على نماذج أولية من نوع الشخص، فيمكنها أن ترث خصائص مشتركة، مع إضافة وإعادة تعريف الخصائص التي تحتاج إلى أن تكون مختلفة.
في المقال التالي، سنناقش الوراثة إلى جانب الميزات الأساسية الأخرى للغات البرمجة الموجهة للكائنات، وسنرى كيف يدعمها جافا سكريبت.
نتيجة
تتناول هذه المقالة نماذج كائنات جافا سكريبت، بما في ذلك كيفية سماح سلاسل نماذج الكائنات للكائنات بتوريث الخصائص من بعضها البعض، وخاصية النموذج الأولي وكيفية استخدامها لإضافة طرق إلى المنشئات، ومواضيع أخرى ذات صلة.









