ال Object prototypes

This translation is in progress.

ال Prototypes هي الآلية التي تعتمدها الجافاسكريبت للوراثة، بمعنى آخر، يمكننا من خلال ال Prototypes جعل كائن يرث مميزات كائن آخر. وهي تعمل بشكل مختلف عن مفهوم آلية الوراثة في اللغات الغرضية التوجة الكلاسيكية. في هذا الدرس سنكتشف هذا الاختلاف، وسنرى كيف تعمل ال prototype chains، وسنلقي نظرة حول كيفية إضافة وظيفة معينة لـ constructors موجودة عن طريق الخاصية prototype. 

المتطلبات الأساسية : دراية لا بأس بها بخصوص الحاسوب، الإلمام بمبادئ وأساسيات الـ HTML وCSS وجافاسكريبت (راجع First steps and Building blocks) وأساسيات بناء الكائنات في الجافاسكريبت OOJS (راجع Introduction to objects).
الهدف : فهم جافاسكريبت object prototypes، وكيف تعمل ال  prototype chains، وكيفية إضافة وظائف جديدة على الخاصية prototype.

الجافا سكريبت لغة تعتمد على ال prototype ؟

غالبا ما توصف جافاسكريبت، على أنها لغة تعتمد على البروتوتايب (prototype-based language)، بمعنى، ان كل كائن في الجافاسكريبت يحتوي على الكائن prototype، الذي يرث خصائص ووظائف الكائن. الكائن prototype يمكنه أيضا أن يحتوي على كائن prototype خاص به، ويرث خصائصه ووظائفه ايضا، وهكذا ودواليك. يعرف هذا السلوك بال prototype chain (سلسلة النمودج). وهذا يشرح، كيف انه بإمكان كائن معين ان يصل لخصائص ووظائف مُعَرَّفة في كائن اخر.

حسنا، حتى نكون أكثر دقة، الخصائص والوظائف مُعَرَّفة في الـ constructor functions لل Object وليس في الـ object instances (مثيلات الكائن).

بالنسبة للبرمجة الكلاسيكية غرضية التوجه classic OOP، يتم تعريف الكلاسات، بعد أن يتم إنشاء object instances، ويتم نسخ كافة الخصائص والوظائف المعرفة في الكلاس فوق ال instance.
في الجافاسكريبت، الأمر مختلف، حيث يتم تكوين رابط بين الـ object instance والـ constructor الخاص به. يمكن تشبيه الأمر بتكوين (حلقة في سلسلة ال prototype)، ويتم الوصول إلى الخصائص والوظائف من الـ constructor، من خلال التنقل صعودا في السلسلة. بمعنى اخر، سيبدأ مُفسِّر الجافاسكريبت بالبحث داخل النمادج prototypes، عن الخاصّية او الوظيفة المطلوبة، من اسفل سلسلة الوراثة، ويكمل بالبحث حتى يصل الى اعلاها.

دعونا نلقي نظرة على هذا المثال ليصبح الأمر أكثر وضوحا.

فهم ال prototype objects

لنَـعُد إلى المثال الذي انتهينا من كتابتة Person() constructor، قم بتحميل المثال التالي في المتصفح الخاص بك، إذا لم يكن لديك المثال الذي عملنا عليه في الدرس السابق استخدم المثال الخاص بنا oojs-class-further-exercises.html او (قم بنسخ source code).

في هذا المثال قمنا بتعريف constructor function، كالتالي :

function Person(first, last, age, gender, interests) {

  // property and method definitions

};

ثم قمنا بإنشاء object instance منه هكذا :

var person1 = new Person('Bob', 'Smith', 32, 'male', ['music', 'skiing']);

إذا قمت بكتابة ".person1" في console الجافاسكريبت، ستلاحظ ان المتصفح يقوم بالاكمال التلقائي ويظهر اسماء الاعضاء الموجودة في هذا الكائن.  

في هذه القائمة، نرى الاعضاء المعرفة في الكائن prototype الخاص ب person1، الذي هو ال Person() constructor، ومكوناته هي name, age, gender, interests, bio و greeting. كما نرى ايضا بعض الأعضاء الآخرين مثل watch ،valueOf،...الخ. وهي معرفة في الكائن prototype لل Person() constructor ، الذي هو Object،
وهذا يوضح لنا، كيف يعمل ال prototype chain.

فماذا سيحدث إذا قمنا باستدعاء وظيفة على ال person1، وهي احدى الوظائف المعرفة في ال Object؟. على سبيل المثال:

person1.valueOf()

ببساطة فهذه الوظيفة ستعود بقيمة الكائن الذي تم استدعاؤه، حاول تجربته وانظر ما الذي سيحدث.

في هذه الحالة:

  • في البداية سيقوم المتصفح بالتحقق ما اذا كان الكائن person1 يحتوي على الوظيفة ()valueOf. (لاحظ هنا، بدا البحث من اسفل السلسلة كما ذكرنا اعلاه).
  • اذا لم يتم ذالك، سيقوم المتصفح بالتحقق ما اذا كان الكائن prototype للكائن person1 الذي هو ال (Person) يحتوي على الوظيفة ()valueOf
  • اذا لم يتم ذالك ايضا، سيقوم المتصفح بالتحقق ما اذا كان الكائن prototype لل Person() constructor الذي هو (Object) يحتوي على الوظيفة ()valueOf،
    اذا تم، ستستدعى الوظيفة المطلوبة للعمل عليها، وكل شئ على ما يرام. اذا لم يجد ما يبحث عنه من ادنى السلسلة الى اعلاها، سيعود ب nullnull هي اعلى السلسلة).

تذكير: الخصائص والوظائف لا يتم نسخها من كائن إلى آخر في ال ptototype chain —
بل يتم الوصول إليها من خلال التنقل صعودا في السلسلة كما هو موضح أعلاه.

ملاحظة:  رسميا لا توجد طريقة للوصول مباشرة الى الكائن prototype داخل كائن. "الروابط" بين العناصر موجودة في السلسلة ويتم تعريفها في خاصية داخلية، يشار إليها ب [[prototype]] في مواصفات لغة جافا سكريبت  (see ECMAScript). ومع ذلك فمعظم المتصفحات الحديثة تحتوي على خاصية متاحة لها تسمى __proto__ (لديها 2 underscores في كلا جوانبها), والتي تحتوي على الكائن prototype. على سبيل المثال جرب:
   __person1.__proto  او  __person1.__proto__.__proto
لمشاهدة ما يشبه السلسلة في التعليمات البرمجية!

الخاصية prototype : اين يتم تعريف الأعضاء الموروثة

اذاً، أين يتم تعريف الخصائص والوظائف الموروثة؟
إذا نظرتم إلى صفحة مرجع ال Object سترون هناك في الجانب الأيسر عددا كبيرا من الخصائص والوظائف المدرجة، البعض منها موروثة، واخرى غير موروثة، لم هذا؟

الجواب، هو أن الاعضاء الموروثة هي تلك المعرفة في الخاصية prototype  (يمكن استدعاؤها تحت ال namespace)، اي تلك التي تبدأ ب .Object.prototype وليست تلك التي تبدأ بمجرد .Object، قيمة الخاصية prototype هي كائن، والتي هي في الاساس حزمة او وعاء لتخزين الخصائص والوظائف التي نريدها أن تكون موروثة من قبل الكائنات السفلية في ال prototype chain.

حتى ال ()Object.prototype.watch() او  Object.prototype.valueOf، متاحة لأي نوع من أنواع الكائنات التي ترث من ال Object.prototype بما في ذلك ال object instances الجديدة التي يتم إنشاؤها من ال constructor، (ك person1 مثلا).

()Object.is() ،Object.keys، والأعضاء الاخرى غير معرفة داخل حزمة ال prototype وهي غير موروثة بواسطة ال object instances أو أنواع الكائنات التي ترث من ال ()Object. هي خصائص ووظائف متاحة فقط لل Object() constructor نفسه.

ملاحظة: يبدو هذا غريبا - كيف يمكن أن يكون لديك وظيفة معرفة في ال constructor،  الذي هو في حد ذاته function؟ حسنا، هي ايضا function من النوع object - راجع مرجع ال Function() constructor حتى تتاكد بنفسك.

يمكنك التحقق من خصائص ال prototype الموجودة بنفسك، ارجع الى مثالنا السابق وحاول ادخال السطر التالي في الجافاسكريبت console:

Person.prototype

المخرجات لن تظهر لك الكثير, لاننا حتى الان، لم تقم بتعريف أي شيء في ال prototype داخل ال constructor الخاص بنا!، بشكل افتراضي، دائماً يبدأ ال prototype داخل ال constructor فارغا. الآن جرب ما يلي:

Object.prototype

سترى عددا كبيرا من الوظائف المعرفة في الخاصية prototype الخاصة ب Object، ثم التي تتوفر على على الكائنات التي ترث من ال Object، كما وضحنا في وقت سابق.

سترى أمثلة أخرى لوراثة ال prototype chain في جميع أنحاء جافا سكريبت، حاول البحث في الوظائف والخصائص المعرفة في prototype الكائنات العامة String, Date, Number, و Array  على سبيل المثال. كلها تحتوي على عدد من الأعضاء المعرفة في ال prototype  الخاص بها، على سبيل المثال عندما نقوم بإنشاء سلسلة نصية (string)، مثل هذه:

var myString = 'This is my string.';

myString، فور انشائها سيتاح لها العديد من الوظائف المفيدة، مثل ()split و ()indexOf و ()replace ... الخ.

هام:  الخاصية prototype هي واحدة من أكثر الاجزاء ارباكا لجافا سكريبت، قد تعتقد أن this تشير الى الكائن prototype  للكائن الحالي، لكن ليس هذا ما يحدث (تذكر ان هذا كائن داخلي  يمكن الوصول إليه بواسطة ال __proto__ ). ال prototype هي خاصية تحتوي على الكائن الذي ستعرف فيه الاعضاء التي تريدها أن تكون موروثة.

إعادة النظر في الوظيفة ()create

راينا سابقا كيف يمكن استخدام الوظيفة ()Object.create لانشاء object instance.

على سبيل المثال، حاول تجربة هذا في console الجافا سكريبت في المثال الخاص بك:

var person2 = Object.create(person1);

ماتفعله الوظيفة ()create في الواقع هو انشاء كائن جديد، من الكائن prototype المحدد، في هذه الحالة، سيتم إنشاء person2 باستخدام person1 على شكل كائن prototype، يمكنك التحقق من ذلك عن طريق إدخال ما يلي في ال console :

person2.__proto__

النتيجة: إرجاع الكائن person1.

الخاصية  constructor

كل object instance يحتوي على الخاصية constructor والتي تشير إلى ال constructor function الأصلي الذي أنشأ ال instance.

على سبيل المثال، حاول ادخال هذه الأوامر في ال console:

person1.constructor
person2.constructor

المفروض على كل منهما العودة لل Person() constructor، كما سيحتوي كل منهما على التعريف الأصلي لهذه ال instances.

خدعة ذكية، يمكنك وضع الاقواس في نهاية الخاصية constructor (مع البرامترات المطلوبة) لانشاء object instance اخر من هذا ال constructor، ال constructor هو في النهاية function، لذالك يمكنك استدعاؤها باستخدام الأقواس، ستحتاج فقط إلى تضمين الكلمة المحجوزة new لتحديد أنك تريد استخدام هذه ال function ك constructor.

حاول ادخال هذا في ال console:

var person3 = new person1.constructor('Karen', 'Stephenson', 26, 'female', ['playing drums', 'mountain climbing']);

الآن حاول الوصول إلى خصائص الكائن الجديد الخاص بك، على سبيل المثال:

person3.name.first
person3.age
person3.bio()

هذا سيعمل بشكل جيد. وعلى الارجح لن تحتاج لاستخدامه في كثير من الأحيان، ولكن يمكن أن يكون مفيدا حقا، عندما تريد إنشاء instance جديد لا يشير إلى ال constructor الأصلي المتاح.

الخاصية constructor لها استخدامات أخرى كذالك. على سبيل المثال، إذا كان لديك
 object instance وترغب في إرجاع اسم ال contructor وهو instance، يمكنك استخدام ما يلي:

instanceName.constructor.name

جرب هذا، على سبيل المثال:

person1.constructor.name

التعديل على ال prototypes

دعونا نلقي نظرة على مثال يقوم بتعديل الخاصية prototype الخاصة بال constructor.

نعود إلى مثالنا oojs-class-further-exercises.html قم بعمل نسخة من الكود التالي source code. اسفل كود الجافاسكريبت، اضف التعليمة البرمجية التالية، وهي خاصة باظافة وظيفة جديدة على constructor الخاصية prototype :  

Person.prototype.farewell = function() {
  alert(this.name.first + ' has left the building. Bye for now!');
}

احفظ التعليمات البرمجية وقم بتحميل الصفحة في المتصفح، وحاول إدخال ما يلي في حقل المدخلات الخاص بك:

person1.farewell();

ستحصل على رسالة تنبيه معروضة (alert)، تضم اسم الشخص كما هو محدد داخل ال constructor. وهذا مفيد حقاً، ولكن الاكثر افائدة هو أنه سيتم تحديث سلسلة الوراثة باكملها بشكل حيوي، و بشكل تلقائي يتم جعل الوظيفة الجديدة متاحة لجميع ال object instances المشتقة من ال constructor.

لنفكر في هذا للحظة. في التعليمة البرمجية خاصتنا، قمنا بتعريف constructor،
ثم قمنا بانشاء instance object من هذا ال constructor، ثم قمنا باظافة الوظيفة الجديدة على constructor الخاصية prototype.

function Person(first, last, age, gender, interests) {

  // property and method definitions

};

var person1 = new Person('Tammi', 'Smith', 32, 'neutral', ['music', 'skiing', 'kickboxing']);

Person.prototype.farewell = function() {
  alert(this.name.first + ' has left the building. Bye for now!');
}

ولكن الوظيفة ()farewell، لا تزال متاحة في ال person1 object instance  — تم تحديث الوظائف المتاحة لها تلقائيا، هذا يثبت ما قلناه سابقا عن ال prototype chain. والمتصفح سيبحث صعودا في السلسلة لإيجاد الوظائف التي لم يتم تعريفها في ال object instance نفسها، بدلا من تلك الوظائف التي يتم نسخها إلى ال instance. وهذا يجعلها قوية جداً، و نظامها الوظيفي قابل للتوسع/للتمدد.

Note: إذا كنت تواجه صعوبة في الحصول على هذا العمل, الق نظرة على المثال الخاص بنا oojs-class-prototype.html  (شاهد ايضا running live ).

نادرا ما ستشاهد خصائص معرفة في الخاصية prototype، على سبيل المثال يمكن إضافة خاصية جديدة كالتالي:

Person.prototype.fullName = 'Bob Smith';

ولكن هذه ليست مرنة جداً، حيث ان ال person لا يمكنه استدعاءها، من الافضل القيام بذلك من خلال بناء fullName خارج name.first و name.last:

Person.prototype.fullName = this.name.first + ' ' + this.name.last;

ومع ذالك فهي لن تعمل، في هذه الحالة this ستشير الى النطاق العام (global scope). وليس الى نطاق الدالة (function scope). استدعاء هذه الخاصية سيعود ب undefined undefined. سيعمل هذا بشكل جيد في الوظائف التي عرفناها سابقا في ال prototype لانها توجد داخل ال function scope، والتي ستنقل بنجاح إلى نطاق مثيل الكائن (object instance scope). لذا قد تقوم بتعريف خصائص ثابتة (constant properties — أي تلك التي لا تحتاج للتغيير) في ال prototype، ولكن عموما فهي تعمل بشكل أفضل لتعريف خصائص داخل ال constructor.

في الواقع، النمط الشائع إلى حد كبير بخصوص تعريف الكائن، هو ان يتم تحديد الخصائص داخل ال constructor، والوظائف على ال prototype. وهذا يجعل الكود اسهل في القراءة، كما سيحتوي ال constructor على الخصائص المعرفة فقط، ويتم تقسيم الوظائف في بلوكات منفصلة، مثلا:

// Constructor with property definitions

function Test(a,b,c,d) {
  // property definitions
};

// First method definition

Test.prototype.x = function () { ... }

// Second method definition

Test.prototype.y = function () { ... }

// etc.

هذا النمط من العمل يمكن ان نراه في Piotr Zalewa's school plan app.

Summary

غطت هذه المادة، جافاسكريبت object prototypes، وكيف يسمح ال prototype object chains للكائنات أن ترث الميزات من بعضها البعض، وكيف يمكن استخدام الخاصية prototype لإضافة وظائف جديدة على ال constructors، وغيرها من الموضوعات ذات الصلة.

في المقال القادم سنرى كيفية تنفيذ الوراثة بين اثنين من الكائنات الخاصة بك.

Document Tags and Contributors

 Contributors to this page: Youssef-Belmeskine, atefBB
 Last updated by: Youssef-Belmeskine,