ال Object prototypes
ال 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
، الذي يرث خصائص ووظائف ال
Object
. الكائن prototype
يمكنه أيضا أن يحتوي على كائن prototype
خاص به، ويرث خصائصه ووظائفه ايضا، وهكذا ودواليك. يعرف هذا السلوك بال prototype chain (سلسلة النمودج). وهذا يشرح، كيف انه بإمكان كائن معين ان يصل لخصائص ووظائف مُعَرَّفة في كائن اخر.
حسنا، حتى نكون أكثر دقة، الخصائص والوظائف مُعَرَّفة في الـ Objects' constructor functions وليس في الـ 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
this.frist = frist;
//etc..
};
ثم قمنا بإنشاء object instance منه هكذا :
var person1 = new Person('Bob', 'Smith', 32, 'male', ['music', 'skiing']);
إذا قمت بكتابة ".person1
" في console الجافاسكريبت، ستلاحظ ان المتصفح يقوم بالاكمال التلقائي ويظهر اسماء الاعضاء الموجودة في هذا الكائن.
في هذه القائمة، نرى الاعضاء المعرفة في ال Person() constructor، الذي يمثله ال person1
وهذه الاعضاء هي name, age, gender, interests, bio
و greeting
. كما نرى ايضا بعض الأعضاء الآخرين مثل watch ،valueOf
،...الخ. وهي معرفة في prototype
ال Person() constructor، ال prototype
الخاص بال Person() constructor هو ال Object
نفسه.
والرسم التبياني التالي يوضح لنا، كيفة عمل ال prototype chain.
فماذا سيحدث إذا قمنا باستدعاء وظيفة على ال person1
، وهي احدى الوظائف المعرفة في ال Object؟. على سبيل المثال:
person1.valueOf()
ببساطة فهذه الوظيفة ()
valueOf
مملوكة لل Object
وبما ان بروتوتايب ال ()Person
هو ال Object
وال person1
هو من يمثله فسيرث ال person1
هذه الخاصية من Object
. مهمة ال valueOf
هي العودة بقيمة الكائن الذي تم استدعاؤها عليه، حاول تجربتها وسترى ما الذي سيحدث.
في هذه الحالة ستسير عملية البحث على الشكل التالي:
- في البداية سيقوم المتصفح بالتحقق ما اذا كان الكائن
person1
يحتوي على الوظيفة()valueOf
. (لاحظ هنا، بدا البحث من اسفل السلسلة كما ذكرنا اعلاه). - اذا لم يتم ذالك، سيقوم المتصفح بالتحقق ما اذا كان ال Person() constructor يحتوي على الوظيفة
()valueOf
- اذا لم يتم ذالك، سيقوم المتصفح بالتحقق ما اذا كان
prototype
ال Person() constructor يحتوي على الوظيفة()valueOf
- اذا لم يتم ذالك ايضا، سيقوم المتصفح بالتحقق ما اذا كان
__proto__
ال Person() constructor الذي هو (Object) يحتوي على الوظيفة()valueOf
اذا تم، ستستدعى الوظيفة المطلوبة للعمل عليها، وكل شئ على ما يرام. اذا لم يجد ما يبحث عنه من ادنى السلسلة الى اعلاها، سيعود بnull
(وnull
هي اعلى السلسلة).
تذكير: الخصائص والوظائف لا يتم نسخها من كائن إلى آخر في ال ptototype chain —
بل يتم الوصول إليها من خلال التنقل صعودا في السلسلة كما هو موضح أعلاه.
ملاحظة: رسميا لا توجد طريقة للوصول مباشرة الى الكائن prototype داخل كائن. "الروابط" بين العناصر موجودة في السلسلة ويتم تعريفها في خاصية داخلية، يشار إليها ب [[prototype]] في مواصفات لغة جافا سكريبت (see ECMAScript). ومع ذلك فمعظم المتصفحات الحديثة تحتوي على خاصية متاحة لها تسمى __proto__
(لديها 2 underscores في كلا جوانبها), والتي تحتوي على الكائن prototype. على سبيل المثال جرب:
__
person1.__proto
او __eprson1.__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 حتى تتاكد بنفسك.
يمكنك التحقق من خصائص البروتوتايب الموجودة بنفسك، ارجع الى مثالنا السابق وحاول ادخال السطر التالي في الجافاسكريبت console:
Person.prototype
المخرجات لن تظهر لك الكثير، لاننا حتى الان، لم نقم باضافة أي شيء في بروتوتايب ال constructor الخاص بنا، بشكل افتراضي، دائماً يبدأ بروتوتايب ال constructor فارغا. الآن جرب ما يلي:
Object.prototype
سترى عددا كبيرا من الوظائف المعرفة في الخاصية prototype
الخاصة بال Object
، ثم التي تتوفر على الكائنات التي ترث من ال Object
، كما وضحنا في وقت سابق.
سنرى أمثلة أخرى للوراثة عن طريق سلسلة البروتوتايب prototype chain
في جميع أنحاء جافا سكريبت، حاول البحث في الوظائف والخصائص المعرفة في بروتوتايب الكائنات العامة ك String
،Date
،Number
، و Array
على سبيل المثال. كلها تحتوي على عدد من الأعضاء المعرفة في البروتوتايب الخاص بها، على سبيل المثال عندما نقوم بإنشاء سلسلة نصية (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 :
Person.prototype.farewell = function() {
alert(this.name.first + ' has left the building. Bye for now!');
}
احفظ التعليمات البرمجية وقم بتحميل الصفحة في المتصفح، وحاول إدخال ما يلي في حقل المدخلات الخاص بك:
person1.farewell();
ستحصل على رسالة تنبيه معروضة (alert)، تضم اسم الشخص كما هو محدد داخل ال constructor. وهذا مفيد حقاً، ولكن الاكثر افائدة هو أنه سيتم تحديث سلسلة الوراثة باكملها بشكل حيوي، و بشكل تلقائي يتم جعل الوظيفة الجديدة متاحة لجميع ال object instances المشتقة من ال constructor.
لنفكر في هذا للحظة. في التعليمة البرمجية خاصتنا، قمنا بتعريف ال Person constructor
ثم قمنا بانشاء instance object من هذا ال Person constructor وهو ال person1 ثم قمنا باظافة الوظيفة الجديدة على بروتوتايب ال Person constructor.
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، وغيرها من الموضوعات ذات الصلة.
في المقال القادم سنرى كيفية تنفيذ الوراثة بين اثنين من الكائنات الخاصة بك.