الوراثة في جافاسكريبت

This translation is incomplete. Please help translate this article from English.

مع معظم التفاصيل المثيرة في OOJS التي درسناها حتى الآن، سنتطرق في هذا الدرس لكيفية إنشاء
(child" object classes (constructors" الذي يرث الميزات من أبيه parent" classes". بالإضافة إلى ذلك، سنقدم بعض النصائح، حول متى وأين يجب عليك استخدام ال OOJS.

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

الوراثة النمودجية - Prototypal inheritance

لقد راينا حتى الآن بعض عمليات الوراثة — وراينا كيف تعمل ال prototype chains، وكيف يتم توارث الاعضاء صعودا في السلسلة، ولكن معظمها اقتصر على الدوال المدمجة في المتصفح (built-in browser functions). الان، سنرى كيف يمكننا إنشاء كائن يرث من كائن آخر في جافا سكريبت؟

كما ذكرنا في وقت سابق من هذه الدورة التدريبية، بعض الاشخاص يعتقدون أن جافاسكريبت ليست لغة كائنية حقيقية. في لغات "OO الكلاسيكية"، حيث يجب تعريف class objects من نوع ما، بعد ذلك يمكنك ببساطة تعريف الكلاس الذي سترث منه الكلاسات الأخرى (شاهد بعض الامثلة البسيطة بخصوص الوراثة في لغة  C++ inheritance). الجافا سكريبت تستخدم نظام مختلف — نظام الربط بين الكائنات — في الجافسكريبت لا يتم نسخ الوظائف الموروثة على الكائنات التي تقوم  بالوراثة، بدلا من ذالك يتم عمل رابط بينها — الوظائف التي ترث مرتبطة عن طريق ال prototype chain (غالباً ما يشار لها بال prototypal inheritance).

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

الشروع في العمل

اولا وقبل كل شئ، قم بعمل نسخة من الملف الخاص بنا oojs-class-inheritance-start.html (او شاهد running live). ستجد داخله نفس المثال Person() constructor الذي استخدمناه طوال هذه الدورة التدريبية مع اختلاف طفيف — لقد قمنا بتعريف الخصائص فقط، داخل ال constructor:

function Person(first, last, age, gender, interests) {
  this.name = {
    first,
    last
  };
  this.age = age;
  this.gender = gender;
  this.interests = interests;
};

وكافة الوظائف معرفة على constructor ال prototype، على سبيل المثال:

Person.prototype.greeting = function() {
  alert('Hi! I\'m ' + this.name.first + '.');
};

سنقوم بإنشاء Teacher class، مثل الذي قمنا بتوصيفه في تعريف البرمجة غرضية التوجه الاولية الخاصة بنا —  Teacher class سيرث كافة الأعضاء من ال Person،  وسيشتمل أيضا على:

  1. خاصية جديدة، subject  — والتي ستحتوي على المادة التي يدرسها المدرس.
  2. وعلى الوظيفة ()greeting محدثة، والتي تبدو أكثر رسمية من الوظيفة ()greeting القياسية —
    وهي اكثر ملائمة مع المدرس الذي يقوم بتدريس بعض الطلاب في المدرسة.

تعريف ال Teacher() constructor function

أول شيء يتعين علينا القيام به هو إنشاء Teacher() constructor —  اضف ما يلي أدناه في التعليمات البرمجية خاصتك:

function Teacher(first, last, age, gender, interests, subject) {
  Person.call(this, first, last, age, gender, interests);

  this.subject = subject;
}

تبدو هذه مشابهة لل Person constructor  في نواح كثيرة، ولكن هناك شيء غريب والذي لم نره من قبل — الدالة ()call . هذه الدالة تسمح لك باستدعاء دالة اخرى معرفة في مكان آخر، ولكن في السياق الحالي، البراميتر الاول لتحديد قيمة this، اي هذا الذي نريد استخدامه عند تشغيل الدالة، والبرامترات الاخرى لتحديد البرامترات التي يجب ان تمرر للدالة عند تشغيلها.

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

في هذه الحالة، وعلى نحو فعال قمنا بتشغيل ال Person() constructor function داخل ال Teacher() constructor function (انظر اعلاه). ما نتج عنه نفس الخصائص المعرفة داخل ()Teacher ولكن باستخدام قيم البرامترات التي تم تمريرها إلى ()Teacher وليس الى ()Person (ببساطة قمنا بتمرير this الى ()call وهذا يعني أن this داخل ()cal ستشير الى Teacher() function).

السطر الأخير داخل ال constructor يعرف الخاصية subject الجديدة وهي خاصة ب ()Teacher فقط.

كملاحظة، بإمكاننا ببساطة القيام بذلك على هذا النحو :

function Teacher(first, last, age, gender, interests, subject) {
  this.name = {
    first,
    last
  };
  this.age = age;
  this.gender = gender;
  this.interests = interests;
  this.subject = subject;
}

ولكن هذا مجرد إعادة تحديد الخصائص من جديد، و ()Teacher لا يرثها من ال ()Person، لذا لا فائدة لما نحاول القيام به. لانه سيأخذ المزيد من السطور في التعليمات البرمجية لا اكثر.

إعداد Teacher() prototype ومرجع ال constructor

كل شيء جيد حتى الآن، ولكن لدينا مشكلة. قمنا بتعيين constructor جديد، وبشكل افتراضي يحتوي على الخاصية prototype فارغة. ونحن بحاجة لجعل ()Teacher يرث الوظائف المعرفة في ال Person() prototype. فكيف نفعل ذلك؟

أضف السطر التالي اسفل التعليمة البرمجية الخاصة بك:

Teacher.prototype = Object.create(Person.prototype);

وهنا يأتي دور الوظيفة ()create للإنقاذ مرة أخرى — في هذه الحالة استخدمناها لإنشاء قيمة جديدة للخاصية prototype (وهي في حد ذاتها كائن يحتوي على خصائص ووظائف) مع prototype يساوي Person.prototype، وتعيينها كقيمة ل Teacher.prototype، وهذا يعني أن Teacher.prototype الآن سوف يرث كل الوظائف الموجودة في ال Person.prototype.

نحن بحاجة إلى فعل شيئا آخر قبل أن نكمل — constructor الخاصية prototype ل ()Teacher
هو الان على شكل ()Person، بسبب الطريقة التي ورثناها منه (لمزيد من المعلومات حول هذا Stack Overflow post) — حاول حفظ التعليمات البرمجية الخاصة بك، واعد تحميل الصفحة من المتصفح، وادخل هذا في console الجافاسكريبت للتحقق:

Teacher.prototype.constructor

وهذا يمكن أن يسبب مشكلة، لذلك نحن بحاجة لتصحيح هذا الوضع، يمكنك القيام بذلك بالرجوع إلى التعليمات البرمجية الخاصة بك وإضافة السطر التالي في الجزء السفلي:

Teacher.prototype.constructor = Teacher;

الآن إذا قمت بحفظ وتحديث، وإدخال Teacher.prototype.constructor سيتم إرجاع ()Teacher، كما هو مطلوب.

منح ()Teacher الوظيفة ()greeting الجديدة

لانهاء الكود الخاص بنا، سنحتاج إلى تعريف الوظيفة الجديدة ()greeting في Teacher() constructor.

أسهل طريقة للقيام بذلك، هو تعريفها على Teacher() prototype — اضف ما يلي في الجزء السفلي من التعليمات البرمجية الخاصة بك:

Teacher.prototype.greeting = function() {
  var prefix;

  if(this.gender === 'male' || this.gender === 'Male' || this.gender === 'm' || this.gender === 'M') {
    prefix = 'Mr.';
  } else if(this.gender === 'female' || this.gender === 'Female' || this.gender === 'f' || this.gender === 'F') {
    prefix = 'Mrs.';
  } else {
    prefix = 'Mx.';
  }

  alert('Hello. My name is ' + prefix + ' ' + this.name.last + ', and I teach ' + this.subject + '.');
};

تحية المدرس هذه (alert)، يستخدم فيها أيضا الاسم prefix المناسب لجنسهم، بناءا على عبارة شرطية.

تجربة المثال

لقد ادخلت الآن كافة التعليمات البرمجية، حاول انشاء object instance من ()Teacher، عن طريق وضع ما يلي في الجزء السفلي لجافا سكريبت الخاصة بك :

var teacher1 = new Teacher('Dave', 'Griffiths', 31, 'male', ['football', 'cookery'], 'mathematics');

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

teacher1.name.first;
teacher1.interests[0];
teacher1.bio();
teacher1.subject;
teacher1.greeting();

ستعمل جميعها بشكل جيد، الثلاثة الأولى ستصل إلى الأعضاء التي تم وراثتها من Person() constructor (class العام فيما الاثنان المتبقيان سيصلان إلى الأعضاء الموجودة في  (Teacher() constructor (class.

ملاحظة : إذا واجهتك مشكلة في الحصول على هذا العمل، قارن التعليمات البرمجية الخاصة بك مع خاصتنا  finished version (شاهد running live ).

التقنية التي قمنا بتغطيتها هنا ليست الطريقة الوحيدة لإنشاء كلاسات موروثة في جافا سكريبت، ولكنها تعمل بشكل جيد، وتعطيك فكرة جيدة عن كيفية تنفيذ الوراثة في جافا سكريبت.

قد يهمك ايضا الاطلاع على بعض المستجدات ECMAScript بخصوص المميزات الجديدة التي تسمح لنا بالقيام بوراثة أكثر نظافة في جافا سكريبت (شاهد Classes). ونحن لم نقم بتغطيتها هنا، كما لم يتم حتى الآن اعتمادها على نطاق واسع جداً عبر المتصفحات. كل التعليمات البرمجية الأخرى التي ناقشناها في هذه المجموعة من المقالات مدعومة بقدر ما، باستثناء IE9 وإلاصدارات السابقة،  هناك طرق لتحقيق الدعم لما سبق.

الطريقة الشائعة هي استخدام مكتبة جافا سكريبت — معظم الخيارات الشائعة تحتوي على مجموعة سهلة من الوظائف المتاحة لتنفيذ الوراثة بسهولة وسرعة. CoffeeScript على سبيل المثال، توفر class ،extends،... الخ.

اختبر قدراتك

في القسم النظري الخاص بال OOP theory section، أدرجنا أيضا Student class كمفهوم، والذي يرث كافة ميزات ال Person، ويحتوي على الوظيفة ()greeting بتحية مختلفة للشخص، وتحيته أكثر رسمية بكثير من تحية المدرس. الق نظرة على ما يشبه تحية الطالب في هذا القسم، وحاول تنفيذ Student() constructor خاص بك، على أن يرث كل ميزات ال ()Person، وقم بتنفيذ وظيفة ()greeting مختلفة.

ملاحظة : إذا واجهتك مشكلة في الحصول على هذا العمل، قارن التعليمات البرمجية الخاصة بك مع خاصتنا finished version (شاهد ايضا running live).

Object member summary

تلخيص، لديك ثلاثة أنواع من property/method لتهتم بشانهم.

  1. تلك المعرفة داخل ال constructor function والتي تمرر لل object instances. هذه سهلة إلى حد ما، في التعليمات البرمجية الخاصة بك، هناك أعضاء معرفة داخل ال constructor تستخدم نوع السطر this.x = x في built in browser code وهي متاحة فقط للأعضاء في object instances (عادة ما يتم إنشاؤها من خلال استدعاء ال constructor باستخدام الكلمة المحجوزة  new مثل var myInstance = new  myConstructor())
  2. تلك التي تم تحديدها مباشرة على constructor نفسها، وهي متاحة فقط في ال constructor.
    وهذه عادة متوفرة فقط في ال built-in browser objects,
     المعترف بها حاليا بالسلاسل مباشرة على ال constructor،
    لا على ال instance. على سبيل المثال Object.keys().
  3. تلك المحددة في constructor ال prototype، والموروثة من جميع ال instances
    ووراثة فئات الكائن. وتشمل اي عضو معرف على الخاصية Constructor's prototype property
    (مثلا ()myConstructor.prototype.x).

إذا لم تتمكن من استعاب هذا، فلا تقلق فانت حتى الآن لا تزال تتعلم، وسوف تستوعبها اكثر مع الممارسة.

متى تستخدم الوراثة في جافا سكريبت؟

على الارجح بعد هذه المادة الأخيرة، ستقول "يا الهي، هذا معقد"، حسنا، انت على حق، ال prototypes والوراثة تمثل بعض اهم الجوانب الأكثر تعقيداً في جافا سكريبت، لكن قوة ومرونة  الجافا سكريبت تاتي من تركيبة الكائن والوراثة ومن المفيد فهم كيف تعمل.

بطريقة ما، فانت تستخدم الوراثة طوال الوقت، كلما استخدمت الميزات المختلفة ل  Web API (واجهة برمجة تطبيقات الويب) او الخصائص والوظائف المعرفة في كائنات المتصفح المدمجة (built-in browser) التي تقوم باستدعائها على السلاسل النصية الخاصة بك، او على المصفوفات...الخ، بشكل فعلي، فانت تستخدم الوراثة.

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

ملاحظة: بسبب طريقة عمل الجافاسكريبت مع ال prototype chain ...الخ، غالباً ما تسمى مشاركة الوظائف بين الكائنات ب delegation (الوفد)، الكائنات المتخصصة تفوض هذه الوظائف إلى نوع الكائن العام. وربما هذا هو الوصف الاكثر دقة لوصف الوراثة، كما  لا يتم نسخ الوظائف "الموروثة" على الكائنات التي تقوم  "بالوراثة". بدلا من ذلك يتم عمل رابط بينها، لذا  فالوظائف الموروثة لاتزال في الكائن العام.

عند استخدام الوراثة، ينصح ان لا يكون لديك عدة مستويات من الوراثة، وتقوم بتتبع دقيق حيث يمكنك تحديد الخصائص والوظائف الخاصة بك. من الممكن البدء بكتابة التعليمات البرمجية والقيام بتعديل prototypes الكائنات المدمجة في المتصفح مؤقتاً، ولكن يجب أن لا تفعل ذلك إلا إذا كان لديك سبب وجيه حقا. والكثير من الوراثة يمكن أن يؤدي إلى ارتباكات لا نهاية لها، وآلام لا نهاية لها عند محاولة تصحيح هذه التعليمات البرمجية.

في نهاية المطاف، الكائنات ليست سوى شكل آخر من أشكال إعادة استخدام التعليمات البرمجية، مثل الدوال والحلقات...الخ، مع أدوار محددة ومزايا خاصة بها. إذا كنت تقوم بإنشاء مجموعة من المتغيرات والمهام ذات الصلة،  وتريد أن تجعلها جميعا معا محزمة بدقة وبعيدة عن مخاطر الاشتباك، فالكائن هو الحل. الكائنات أيضا مفيدة جداً عندما تريد تمرير مجموعة من البيانات من مكان إلى آخر. كل هذه الأمور لا يمكن تحقيقها بدون استخدام ال constructors أو الوراثة. إذا كنت بحاجة إلى instance واحد فقط من الكائن، فمن الافضل استخدام object literal، وبالتأكيد فلن تحتاج للوراثة.

خلاصة

 غطت هذه المادة ما تبقى من نظرية ال OOJS الأساسية، وتركيبة الكائن الاساسية، من خلال هذه المعطيات فعلى الارجح انك اصبحت اكثر قدرة على فهم كائن الجافاسكريبت وأساسيات ال OOP، وايضا ال prototypes والميراث النمودجي (prototypal inheritance)، وكيفية إنشاء كلاسات (اي ما يعادل ال constructors في جافاسكريبت) وال object instances، واضافة ميزات الى الكلاسات، وانشاء كلاسات فرعية (subclasses)، التي ترث من الكلاسات الأخرى.

في الدرس القادم سنلقي نظرة على كيفية التعامل مع ال (JavaScript Object Notation (JSON، وتبادل البيانات المشتركة باستخدام كائنات جافا سكريبت.

See also

  • ObjectPlayground.com — A really useful interactive learning site for learning about objects.
  • Secrets of the JavaScript Ninja, Chapter 6 — A good book on advanced JavaScript concepts and techniques, by John Resig and Bear Bibeault. Chapter 6 covers aspects of prototypes and inheritance really well; you can probably track down a print or online copy fairly easily.
  • You Don't Know JS: this & Object Prototypes — Part of Kyle Simpson's excellent series of JavaScript manuals, Chapter 5 in particular looks at prototypes in much more detail than we do here. We've presented a simplified view in this series of articles aimed at beginners, whereas Kyle goes into great depth and provides a more complex but more accurate picture.

Document Tags and Contributors

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