تُعدّ وراثة الأصناف واحدةً من الطرائق لتوسعة أحد الأصناف لديك، أي أن نقدّم وظائف جديدة لأحد الأصناف علاوةً على ما لديه.
عبارة التوسعة extends
لنقل بأنّ لدينا صنف الحيوان Animal
:
class Animal { constructor(name) { this.speed = 0; this.name = name; } run(speed) { this.speed += speed; alert(`${this.name} runs with speed ${this.speed}.`); // يركض حيوان كذا بالسرعة كذا } stop() { this.speed = 0; alert(`${this.name} stands still.`); // يقف حيوان كذا في مكانه } } let animal = new Animal("My animal");
هكذا نصف كائن الحيوان animal
وصنف الحيوان Animal
في صورة:
ولنقل بأنّنا نريد إضافة صنف آخر… ليكن أرنبًا class Rabbit
.
وطبعًا، فالأرانب حيوانات أيضًا، وعلى صنف الأرانب Rabbit
أن يكون أساسه الحيوان Animal
ليصل إلى توابِع الحيوانات فتقوم الأرانب بما تقوم به الحيوانات ”العادية“ (generic).
صياغة توسعة الصنف إلى صنف آخر هي: class Child extends Parent
(صنف الابن توسعة من صنف الأبّ).
لنصنع صنف الأرنب class Rabbit
ليرث الحيوان Animal
:
class Rabbit extends Animal { // لاحظ hide() { alert(`${this.name} hides!`); // اختفى هكذا! } } let rabbit = new Rabbit("White Rabbit"); // الأرنب الأبيض rabbit.run(5); // White Rabbit runs with speed 5. // يركض حيوان الأرنب بسرعة 5 rabbit.hide(); // White Rabbit hides! // اختفى الأرنب الأبيض!
يمكن لكائن Rabbit
الوصول إلى توابِع Rabbit
(مثل rabbit.hide()
) كما توابِع Animal
(مثل rabbit.run()
).
داخليًا فعبارة extends
تعمل كما تعمل ميكانيكية كائنات prototype المعهودة، فضتبط Rabbit.prototype.[[Prototype]]
ليكون Animal.prototype
. بهذا لو لم يوجد التابِع في كائن Rabbit.prototype
، يأخذه المحرّك من Animal.prototype
.
فمثلًا لنجد التابِع rabbit.run
يبحث المحرّك (من أسفل إلى أعلى، كما الصورة):
-
كائن الأرنب
rabbit
(ليس فيهrun
). -
كائن prototype له، أي
Rabbit.prototype
(فيهhide
وليس فيهrun
). -
كائن prototype له، أي (بسبب
extends
) Animal.prototype
، بهذا يجد تابِعrun
أخيرًا.
كما نذكر من فصل ”“، فمحرّك جافاسكربت نفسه يستعمل التوارث عبر prototype لكائناته المضمّنة في اللغة. فمثلًا كائن Date.prototype.[[Prototype]]
هو الكائن Object.prototype
. لهذا يمكن للتواريخ الوصول إلى توابِع الكائنات العادية.
ملاحظة: تسمح صياغة الصنف ليس بتحديد الصنف فقط وإنما إضافة أي تعبير بعد عبارة extends
. فمثلًا استدعاء التابع التاي سيبني صنف الأب.
function f(phrase) { return class { sayHi() { alert(phrase) } } } class User extends f("Hello") {} new User().sayHi(); // Hello
سيرثُ الصنف class User
من النتيجة للصنف f("Hello")
.
وهذه الميزة مفيدة لكتابة الأنماط البرمجية المتقدمة عندما نستخدم تابع لينشئ صنف صنف اعتمادًا على عدة شروط والتي يمكن أن ترثها.
إعادة تعريف دالة
الآن نتحرّك ونعيد تعريف أحد التوابِع. مبدئيًا فكلّ التوابِع غير المحدّدة في صنف الأرانب تُؤخذ مباشرةً ”كما هي“ من صنف الحيوانات.
ولكن لو حدّدنا تابِعًا معيّنًا في في Rabbit
(وليكن stop()
) فسيُستعمل بدله:
class Rabbit extends Animal { stop() { // الآن سنسخدمها من أجل rabbit.stop() // بدلًا من استخدام stop() من الصنف مباشرة } }
عادةً لا نرغب باستبدال كامل ما في التابِع الأب، بل البناء فوقه أو تعديله أو توسعة وظائفه، أي ننفّذ شيئًا في التابِع ثمّ نستدعي التابِع الأبّ قبل أو بعد ذلك.
ولحسن الحظ فالأصناف تقدّم لنا عبارة "super"
.
-
نستعمل
super.method(...)
لنستدعي تابِعًا أبًا. -
ونستدعي
super(...)
لنستدعي الباني الأب (هذا فقط لو كنّا في باني هذه الدالة).
فمثلًا، ليختبئ هذا الأرنب النبه ما إن يتوقّف:
class Animal { constructor(name) { this.speed = 0; this.name = name; } run(speed) { this.speed += speed; alert(`${this.name} runs with speed ${this.speed}.`); } stop() { this.speed = 0; alert(`${this.name} stands still.`); } } class Rabbit extends Animal { hide() { alert(`${this.name} hides!`); } // هنا stop() { super.stop(); // نستدعي stop في الأب this.hide(); // ثمّ نستدعي hide } } let rabbit = new Rabbit("White Rabbit"); rabbit.run(5); // سيركض "White Rabbit" بسرعة 5 rabbit.stop(); // توقف الأرنب .وهو الآن مختبئ
الآن صار داخل الأرنب التابِع Rabbit
الذي يستدعي التابِع stop
من أباه
كما شرحنا في درس "الدوال السهمية" لا يمكننا استخدام الكلمة المفتاحية super
على الدوالّ السهمية.
ولو استطعنا الوصول إليها من خلال الكلمة المفتاحية super
فستكون مأخوذة من الدالّة الخارجية. هكذا:
class Rabbit extends Animal { stop() { setTimeout(() => super.stop(), 1000); // استدعاء الأب سيتوقف بعد ثانية واحدة } }
إن عمل الدلّة stop()
مشابه تمامًا لعمل الكلمة المفتاحية super
مع الدوال السهمية. لذا فإنها تعمل مثلما نريد. ولو حُددت كدالّة عادية فسيظهر لدينا خطأ:
// Unexpected super setTimeout(function() { super.stop() }, 1000);
إعادة تعريف الباني
متى تعاملنا مع البانيات، صار الأمر
لم يكن لصنف الأرنب Rabbit
(حتّى اللحظة) أيّ بانٍ له.
حسبما تقول المواصفة فلو وسّع أحد الأصناف صنفًا آخر ليس له بانيًا، فسيُولّد المحرّك هذا الباني ”الفارغ“:
class Rabbit extends Animal { // يُولّد للأصناف التي تُوسِّع أخرى وليس فيها بانيات constructor(...args) { super(...args); } }
كما نرى فهي تستدعي الباني constructor
الأبّ بتمرير كلّ الوُسطاء إليه، فقط. لا يحدث هذا إلّا لو لم نكتب بانيًا في الصنف الابن.
لنُضف الآن بانيًا من عندنا إلى الأرنب Rabbit
. سيضبط هذا الباني الخاصية earLength
علاوةً على name
:
class Animal { constructor(name) { this.speed = 0; this.name = name; } // ... } class Rabbit extends Animal { // هنا constructor(name, earLength) { this.speed = 0; this.name = name; this.earLength = earLength; } // ... } // لا تعمل! let rabbit = new Rabbit("White Rabbit", 10); // Error: this is not defined.
ماذا؟! هناك خطأ! انتهى الأمر، ”صناعة الأرانب“ لم تعد ممكنة بعد الآن. تراه ما المشكلة؟
باختصار: على البانيات في الأصناف الموروثة استدعاء super(...)
هذا أولًا، وثانيًا استدعاءه قبل استعمال this
.
ولكن لحظة… لماذا؟ ما الذي يجري؟ هذا المطلب غريب حقًا.
بالطبع لا شيء بدون توضيح وشرح، لذا فلنتعمّق داخل التفاصيل ونفهم ما يجري ”حبّة حبّة“.
تضع لغة جافاسكربت خطًا فاصلًا بين الدالة البانية للصنف الموروث (أي ”الباني المشتقّ“) وغيرها من دوال. لهذا الباني خاصية داخلية فريدة اسمها [[ConstructorKind]]:"derived"
، وهي علامة يضعها المحرّك عليه داخليًا خلف الكواليس. تؤثّر هذه ”العلامة“ على سلوك الباني حين نستعمله مع new
.
-
حين نُنفّذ الدوال العادية باستعمال
new
، تُنشِئ لنا كائنًا فارغًا وتضبطه ليكونthis
. - ولكن حين يعمل الباني المشتق، فلا يفعل ذلك، بل يتوقّع من الباني الأبّ القيام بهذه المهمة الصعبة.
لذا على الباني المشتق استدعاء super
ليُنفّذ باني أباه (غير المشتق) وإلّا فلن يُنشأ أيّ كائن يكون this
، بهذا تكون الشيفرة خطأ.
على باني الصنف Rabbit
استدعاء super()
قبل this
ليعمل، هكذا تمامًا:
class Animal { constructor(name) { this.speed = 0; this.name = name; } // ... } class Rabbit extends Animal { constructor(name, earLength) { super(name); // هنا this.earLength = earLength; } // ... } // الآن كلّ شيء كما يجب أن يكون let rabbit = new Rabbit("White Rabbit", 10); alert(rabbit.name); // White Rabbit alert(rabbit.earLength); // 10
عبارة super
: أمور داخلية و[[HomeObject]]
تحذير: سترى معلومات متقدّمة لو كنت تقرأ هذا الدرس أوّل مرّة فيمكنك تخطّي هذا الجزء، إذ يتكلّم عن الميكانيكا الداخلية التي يستعملاها التوارث وعبارة super
.
هيًا ننزل إلى الأعماق ونرى ما خلف كواليس super
. في هذه الرحلة سنرى أمور جميلة أيضًا ”إكسترا سوبر“.
لنوضّحها من البداية: لو أخذت كلّ ما تعلّمناه حتّى الحظة، فما من طريقة لتعمل فيها عبارة super
في أيّ حال من الأحوال!
تمامًا، كما فكّرت الآن، لنطرح السؤال: كيف تعمل هذه العبارة تقنيًا أساسًا؟ متى ما عمل أحد توابِع الكائنات، جلب الكائن الحالي على أنّه this
. فلو استدعينا super.method()
فكلّ ما على المحرّك فعله هو جلب التابِع method
من كائن prototype للكائن الحالي، صحيح؟ أجل ولكن كيف ذلك؟
ربّما ترى المهمة سهلة ولكنّها ليست كذلك البتة. يمكن القول أنّ المحرّك يعلم بالكائن الحالي this
، فيمكنه أن يأخذ تابِع method
في الأب باستعمال this.__proto__.method
. ولكن للأسف فهذا الحلّ ”البسيط“ لن يعمل أبدًا.
لنوضّح المشكلة أولًا، باستعاضة الأصناف لتكون كائنات عادية لتسهيل الفهم.
(لو لم تريد معرفة التفاصيل فتخطّى هذا القسم وانتقل إلى الجزء [[HomeObject]]
، لا مشكلة. أو واصِل معنا في هذه الرحلة الموحشة في أعماق غابة لغة جافاسكربت.)
في المثال أسفله، نرى rabbit.__proto__ = animal
. لنجرّب الآن هذا الأمر: داخل rabbit.eat()
نستدعي animal.eat()
باستعمال this.__proto__
:
let animal = { name: "Animal", eat() { alert(`${this.name} eats.`); } }; let rabbit = { __proto__: animal, name: "Rabbit", eat() { // هذه إحدى طرق عمل super.eat() this.__proto__.eat.call(this); // (*) } }; rabbit.eat(); // Rabbit eats.
أخذنا في السطر (*)
التابِعَ eat
من كائن prototype (animal
) واستدعيناه على أنّ السياق هو الكائن الحالي. لاحظ أهمية .call(this)
إذ لو كتبنا this.__proto__.eat()
فقط فسيُنفّذ التابِع eat
الأب داخل سياق كائنَ prototype، وليس في الكائن الحالي.
وفي الجزء الأول من الشيفرة نرى كلّ شيء يعمل: عمل التابِع alert
كما ينبغي عليه.
حان وقت إضافة كائن آخر إلى السلسلة، وكسر هذه السلسلة إربًا:
let animal = { name: "Animal", eat() { alert(`${this.name} eats.`); } }; let rabbit = { __proto__: animal, eat() { // ...هنا يأكل الأرنب كما تأكل الأرانب، بعدها نستدعي التابِع الأبّ (animal) this.__proto__.eat.call(this); // (*) } }; let longEar = { __proto__: rabbit, eat() { // ...الأرنب ذو الأذن الطويلة يلهو ويلعب، ثمّ نستدعي التابِع الأبّ (rabbit) this.__proto__.eat.call(this); // (**) } }; // هنا!longEar.eat(); // Error: Maximum call stack size exceeded
لم تعد الشيفرة تعمل الآن! نرى خطأً عند استدعاء longEar.eat()
.
قد لا يبدو الأمر جليًا من أوّل نظرة، ولكن لو تعقّبنا استدعاء longEar.eat()
فسنرى الأمر بوضوح، ففي السطرين () و (*) تكون قيمة this
هي الكائن الحالي (longEar
). هذا ضمن الأساسيات، فعلى توابِع الكائنات جلب الكائن الحالي فهو this
، وليس كائنَ prototype أو ما شابهه.
بذلك في السطرين معًا (*) و (**) تكون قيمة this.__proto__
واحدة: الكائن rabbit
، وكلاهما يستدعيان التابِع rabbit.eat
دون أن يرتقيا بالسلسلة، فيدوران في حلقة لا نهاية لها.
إليك عمّا يحدث في صورة:
نرى في التابِع longEar.eat()
عند السطر (**)
استدعاء rabbit.eat
بتمرير this=longEar
.
// نرى داخل longEar.eat() قيمة this = longEar this.__proto__.eat.call(this) // (**) // يصير longEar.__proto__.eat.call(this) // وهو فعليًا rabbit.eat.call(this);
-
وبعدها في السطر
(*)
داخلrabbit.eat
، نحاول تمرير الاستدعاء إلى مستوًى أعلى داخل السلسلة، ولكن قيمةthis=longEar
، بهذا تصير قيمةthis.__proto__.eat
هيrabbit.eat
ثانيةً.
// نرى داخل rabbit.eat() قيمة this = longEar this.__proto__.eat.call(this) // (*) // becomes longEar.__proto__.eat.call(this) // or (again) rabbit.eat.call(this);
-
بهذا… يستدعي
rabbit.eat
نفسه في حلقة لانهاية لها لأنّها يعجز عن الارتقاء في السلسلة.
ما من طريقة لحلّ هذه المشكلة باستعمال this
فقط.
[[HomeObject]]
أضافت لغة جافاسكربت -لحلّ هذه المعضلة- خاصية داخلية (أخرى) للدوال، وهي ”الكائن المنزل“ [[HomeObject]]
.
متى ما حُدّدت الدالة لتكون صنفًا أو تابِعًا لكائن، أصبحت خاصية [[HomeObject]]
للدالة ذلك الصنف أو الكائن.
تستعمل super
هذا الكائن لحلّ كائن prototype الأبّ هو وتوابِعه.
لنرى كيف يعمل هذا الشيء، بالكائنات العادية أولًا:
let animal = { name: "Animal", eat() { // animal.eat.[[HomeObject]] == animal alert(`${this.name} eats.`); } }; let rabbit = { __proto__: animal, name: "Rabbit", eat() { // rabbit.eat.[[HomeObject]] == rabbit super.eat(); } }; let longEar = { __proto__: rabbit, name: "Long Ear", eat() { // longEar.eat.[[HomeObject]] == longEar super.eat(); } }; // يعمل التابِع كما نريد longEar.eat(); // Long Ear eats.
عملت الشيفرة كما المفترض ذلك بسبب آلية عمل [[HomeObject]]
. تعرف التوابِع (مثل longEar.eat
) خاصيةَ [[HomeObject]]
لها وتأخذ التابِع الأبّ من كائن prototype لذاك الكائن، ودون استعمال this
أبدًا.
التوابِع ليست ”حرّة“
كما نعلم فالتوابع -بنحوٍ عام- ”حرّة“ وليست مروبطة بأيّ كائن. فيمكننا نسخها بين الكائنات واستعمالها بتمرير قيمة this
أخرى.
ولكن وجود [[HomeObject]]
يخلّ بهذا المبدأ إذ تتذكّر التوابِع كائناتها الأصلية هكذا. يبقى هذا الارتباط وثيقًا للأبد إذ لا يمكننا تغيير [[HomeObject]]
.
ولكن المكان الوحيد الذي نستعمل فيه خاصية [[HomeObject]]
(في لغة جافاسكربت) هو super
. يعني أنّه لو لم يستعمل التابِع super
فيمكننا عدّه حرًا ونمضي بنسخه وتوزيعه على الكائنات. ولكن متى استعملت super
، ساءت الأمور.
إليك مثالًا كاملًا عن نتيجة خطأ بعد النسخ بسبب super
:
let animal = { sayHi() { console.log(`I'm an animal`); // أنا حيوان } }; // يرث صنف الأرنب صنفَ الحيوان let rabbit = { __proto__: animal, sayHi() { super.sayHi(); } }; let plant = { sayHi() { console.log("I'm a plant"); // أنا نبات } }; // يرث صنف الشجرة صنفَ النبات let tree = { __proto__: plant, sayHi: rabbit.sayHi // (*) }; tree.sayHi(); // أنا حيوان (؟!؟)
باستدعاء tree.sayHi()
نرى الشجرة تقول ”أنا حيوان“. لا، لا! سبب ذلك بسيط جدًا:
-
في السطر
(*)
نسخنا التابِعtree.sayHi
منrabbit
. من يدري، ربّما لنقلّل من تكرار الشيفرات؟ -
وخاصية
[[HomeObject]]
لها هي الصنفrabbit
، إذ صنعنا التابِع داخلrabbit
، وما من طريقة لتغيير[[HomeObject]]
. -
نرى في شيفرة التابِع
tree.sayHi()
الاستدعاءَsuper.sayHi()
، وهو ينتقل إلى أعلى عندrabbit
ويأخذ التابِع منanimal
.
إليك صورة توضّح ما يحدث:
توابِع لا صفات داليّة
تُعرّف اللغة عن خاصيات [[HomeObject]]
للتوابِع في الأصناف وفي الكائنات العادية. ولكن في حالة الكائنات فيجب تعريف التوابِع هكذا تمامًا method()
وليس هكذا "method: function()"
.
قد لا نرى فرقًا جوهريًا في الطريقتين، لكنّ محرّكات جافاسكربت تراه كذلك.
استعملنا في المثال أسفله صياغة ليست بتابِع للموازنة. بهذا لم تُضبط خاصية [[HomeObject]]
ولن تعمل الوراثة:
let animal = { eat: function() { // نكتبها هكذا بدل eat() عمدًا {... // ... } }; let rabbit = { __proto__: animal, eat: function() { super.eat(); } }; rabbit.eat(); // Error calling super (إذ [[HomeObject]] غير موجودة)
خلاصة
-
نستعمل
class Child extends Parent
لتوسعة الأصناف.-
أي أنّ خاصية
Child.prototype.__proto__
ستكونParent.prototype
، فتصير التوابِع موروثة.
-
أي أنّ خاصية
-
عندما نعيد تعريف الباني:
-
علينا استدعاء الباني الأبّ باستعمال
super()
في الباني ”الابن“ ذلك قبل استعمالthis
.
-
علينا استدعاء الباني الأبّ باستعمال
-
عندما نعيد تعريف أي تابع آخر:
-
يمكننا استعمال
super.method()
في التابِع ”الابن“ لاستدعاء التابِع ”الأبّ“.
-
يمكننا استعمال
-
أمور داخلية:
-
تتذكّر التوابِع صنفها/كائنها وتحفظه في خاصية
[[HomeObject]]
الداخلية، هكذا يحلّsuper
التوابِع الأب. -
بذلك يكون ليس من الآمن نسخ تابِع لديه
super
من كائن ووضعه في آخر.
-
تتذكّر التوابِع صنفها/كائنها وتحفظه في خاصية
كما وأنّ:
-
ليس للدوال السهمية لا
this
ولاsuper
تمارين
خطأ في إنشاء سيرورة
الأهمية: 5
إليك الشيفرة التي نُوسّع فيها صنف Rabbit
من صنف Animal
.
للأسف فلا يمكننا صناعة كائنات الأرانب. ما المشكلة؟ أصلِحها في طريقك.
class Animal { constructor(name) { this.name = name; } } class Rabbit extends Animal { constructor(name) { this.name = name; this.created = Date.now(); } } // هنا let rabbit = new Rabbit("White Rabbit"); // Error: this is not defined alert(rabbit.name);
الحل
هذا لأنّ على الباني الابن استدعاء super()
.
إليك الشيفرة الصحيحة:
class Animal { constructor(name) { this.name = name; } } class Rabbit extends Animal { constructor(name) { super(name); // هنا this.created = Date.now(); } } let rabbit = new Rabbit("White Rabbit"); // الآن تمام alert(rabbit.name); // White Rabbit
توسعة ساعة
الأهمية: 5_
لدينا صنف ساعة Clock
، وهو حاليًا يطبع الوقت في كلّ ثانية.
class Clock { constructor({ template }) { this.template = template; } render() { let date = new Date(); let hours = date.getHours(); if (hours < 10) hours = '0' + hours; let mins = date.getMinutes(); if (mins < 10) mins = '0' + mins; let secs = date.getSeconds(); if (secs < 10) secs = '0' + secs; let output = this.template .replace('h', hours) .replace('m', mins) .replace('s', secs); console.log(output); } stop() { clearInterval(this.timer); } start() { this.render(); this.timer = setInterval(() => this.render(), 1000); } }
أنشِئ الصنف الجديد ExtendedClock
ليرث من Clock
وأضِف المُعامل precision
، وهو عدد الملّي ثانية ms
بين كلّ ”تَكّة“. يجب أن يكون مبدئيًا 1000
(أي ثانية كاملة).
-
ضع شيفرتك في الملف
extended-clock.js
. -
تعديل ملف
clock.js
الأصلي ممنوع. وسّع الصنف.
يمكن الاعتماد على هذه البيئة التجريبية لحل التمرين.
الحل
class ExtendedClock extends Clock { constructor(options) { super(options); let { precision = 1000 } = options; this.precision = precision; } start() { this.render(); this.timer = setInterval(() => this.render(), this.precision); } };
الأصناف تُوسّع الكائنات؟
الأهمية: 5
كما نعلم فالكائنات كلها ترث Object.prototype
وتقدر على الوصول إلى توابِع الكائنات ”العادية“ مثل hasOwnProperty
وغيرها.
مثال سريع:
class Rabbit { constructor(name) { this.name = name; } } let rabbit = new Rabbit("Rab"); // التابِع hasOwnProperty مأخوذ من Object.prototype alert( rabbit.hasOwnProperty('name') ); // true
ولكن لو كتبنا ذلك جهارةً هكذا "class Rabbit extends Object"
فالناتج يختلف عن "class Rabbit"
فقط! غريب.
تُراه ما الفرق؟
إليك مثالًا عمّا أقصد (الشيفرة لا تعمل، لماذا؟ أصلِحها!):
class Rabbit extends Object { constructor(name) { this.name = name; } } let rabbit = new Rabbit("Rab"); alert( rabbit.hasOwnProperty('name') ); // true
الحل
أولًا نرى ما مشكلة الشيفرة تلك.
متى شغّلنا الشيفرة بان سبب المشكلة: على باني الأصناف الموروثة استدعاء super()
وإلّا تكون قيمة "this"
”غير معرّفة“.
لذا سنصلحها:
class Rabbit extends Object { constructor(name) { super(); // علينا استدعاء الباني الأب حين نرث الصنف this.name = name; } } let rabbit = new Rabbit("Rab"); alert( rabbit.hasOwnProperty('name') ); // true
ولكن… لم ننتهِ بعد.
حتّى مع هذا … فهناك فرق مهم جوهري بين "class Rabbit extends Object"
وclass Rabbit
.
كما نعلم فصياغة extends
تضبط كائنا prototype:
-
بين توابِع الباني لـِ
"prototype"
(بالنسبة للتوابع العادية). - بين توابِع الباني نفسها (بالنسبة للتوابع الثابتة).
في حالتنا إن class Rabbit extends Object
تعني:
class Rabbit extends Object {} alert( Rabbit.prototype.__proto__ === Object.prototype ); // (1) true alert( Rabbit.__proto__ === Object ); // (2) true
لذا يوفر Rabbit
إمكانية الوصول إلى الدوال الثابتة للكائن Object
. هكذا:
class Rabbit extends Object {} *!* // عادةً نستدعي Object.getOwnPropertyNames alert ( Rabbit.getOwnPropertyNames({a: 1, b: 2})); // a,b */!*
ولكن إذا لم يكن لدينا extends Object
فلن تُسند Rabbit.__proto__
للصنف Object
.
إليك المثال:
class Rabbit {} alert( Rabbit.prototype.__proto__ === Object.prototype ); // (1) true alert( Rabbit.__proto__ === Object ); // (2) false (!) alert( Rabbit.__proto__ === Function.prototype ); // as any function by default *!* // error, no such function in Rabbit alert ( Rabbit.getOwnPropertyNames({a: 1, b: 2})); // Error */!*
في هذه الحالة إن Rabbit
لن يزودنا بطريقة للوصول إلى التوابع الثابتة في Object
.
بالمناسبة يملك النموذج الأولي للتوابع Function.prototype
دوال مُعمَّمة مثل :call
و bind
..إلخ. وهي متاحة دائمًا في كِلا الحالتين، لأن باني Object
المضمن في اللغة هو Object.__proto__ === Function.prototype
.
لذلك وباختصار هناك اختلافان وهما:
class Rabbit | class Rabbit extends Object |
---|---|
-- |
يحتاج لاستدعاءsuper() في الباني
|
Rabbit.__proto__ === Function.prototype
|
Rabbit.__proto__ === Object
|
ترجمة -وبتصرف- للفصل Class inheritance من كتاب The JavaScript language
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.