JavaScript هي لغة موجهة نحو الكائن. هناك قول كلاسيكي للغاية في JavaScript: كل شيء كائن. نظرًا لأنه موجه نحو الكائن ، هناك ثلاث خصائص رئيسية للموجهة نحو الكائن: التغليف ، والميراث ، وتعدد الأشكال. هنا نتحدث عن ميراث JavaScript ، وسوف نتحدث عن الاثنين الآخرين في وقت لاحق.
وراثة JavaScript ليست كما هي مثل C ++. يعتمد ميراث C ++ على الفصل ، في حين يعتمد ميراث JavaScript على النماذج الأولية.
الآن المشكلة هنا.
ما هو النموذج الأولي؟ بالنسبة للنماذج الأولية ، يمكننا الرجوع إلى الفئات في C ++ وحفظ خصائص وطرق الكائن. على سبيل المثال ، دعنا نكتب كائنًا بسيطًا
نسخة الكود كما يلي:
وظيفة Animal (اسم) {
this.name = name ؛
}
animal.prototype.setName = function (name) {
this.name = name ؛
}
var Animal = New Animal ("Wangwang") ؛
يمكننا أن نرى أن هذا حيوان كائن ، والذي يحتوي على اسم سمة وطريقة setName. لاحظ أنه بمجرد تعديل النموذج الأولي ، مثل إضافة طريقة ، ستشارك جميع مثيلات الكائن هذه الطريقة. على سبيل المثال
نسخة الكود كما يلي:
وظيفة Animal (اسم) {
this.name = name ؛
}
var Animal = New Animal ("Wangwang") ؛
في هذا الوقت ، لدى Animal فقط سمة الاسم. إذا أضفنا جملة ،
نسخة الكود كما يلي:
animal.prototype.setName = function (name) {
this.name = name ؛
}
في هذا الوقت ، سيكون لدى Animal أيضًا طريقة setName.
نسخة الميراث - بدءًا من كائن فارغ ، نعلم أنه من بين الأنواع الأساسية من JS ، يوجد نوع يسمى كائن ، ومثاله الأساسي هو كائن فارغ ، أي المثيل الذي تم إنشاؤه عن طريق استدعاء كائن جديد () مباشرة أو معلنته مع الحرفي {}. الكائن الفارغ هو "كائن نظيف" ، مع خصائص وطرق محددة مسبقًا فقط ، في حين أن جميع الكائنات الأخرى موروثة من كائنات فارغة ، لذلك جميع الكائنات لها هذه الخصائص والأساليب المحددة مسبقًا. النموذج الأولي هو في الواقع مثيل كائن. معنى النموذج الأولي هو: إذا كان لدى المُنشئ كائن النموذج A ، فيجب نسخ الحالات التي أنشأها المنشئ من A. نظرًا لأن المثيل يتم نسخه من الكائن A ، فيجب أن يرث المثيل جميع الخصائص والأساليب والخصائص الأخرى لـ A. لذا ، كيف يتم تنفيذ النسخ المتماثل؟ الطريقة 1: يتم نسخ نسخة إنشاء كل مثيل تم إنشاؤه من النموذج الأولي ، ويحتل المثيل الجديد نفس مساحة الذاكرة مثل النموذج الأولي. على الرغم من أن هذا يجعل OBJ1 و OBJ2 "متسقًا تمامًا" مع نماذجهما الأولية ، إلا أنه غير اقتصادي للغاية - سيزداد استهلاك مساحة الذاكرة بسرعة. كما هو موضح في الصورة:
الطريقة 2: نسخ حول كتابة هذه الاستراتيجية تأتي من تكنولوجيا نظام الخداع الثابت: نسخة عن الكتابة. مثال نموذجي على هذا النوع من الاحتيال هو مكتبة الارتباط الديناميكية (DDL) في نظام التشغيل ، الذي يتم نسخ منطقة ذاكرته دائمًا على الكتابة. كما هو موضح في الصورة:
نحتاج فقط إلى تحديد في النظام أن OBJ1 و OBJ2 يعادلان نماذجهما الأولية ، لذلك عند القراءة ، نحتاج فقط إلى اتباع التعليمات لقراءة النموذج الأولي. عندما نحتاج إلى كتابة خصائص كائن ما (مثل OBJ2) ، فإننا نسخّل صورة النموذج الأولي ونشير العمليات اللاحقة إلى الصورة. كما هو موضح في الصورة:
ميزة هذه الطريقة هي أننا لا نحتاج إلى الكثير من النفقات العامة للذاكرة عند إنشاء مثيلات وقراءة سمات. نستخدم فقط بعض التعليمات البرمجية لتخصيص الذاكرة عند الكتابة في المرة الأولى ، ونحضر بعض التعليمات البرمجية والذاكرة. ولكن لن يكون هناك مثل هذا النفقات العامة منذ ذلك الحين ، لأن كفاءة الوصول إلى الصور والوصول إلى النماذج الأولية متسقة. ومع ذلك ، بالنسبة للأنظمة التي تكتب في كثير من الأحيان ، فإن هذه الطريقة ليست اقتصادية من الطريقة السابقة. الطريقة 3: قراءة اجتياز هذه الطريقة تحول تفاصيل النسخ من النموذج الأولي إلى العضو. تتميز هذه الطريقة بنسخ معلومات العضو إلى صورة المثيل فقط عند كتابة عضو في مثيل. عند كتابة خصائص الكائن ، على سبيل المثال (OBJ2.Value = 10) ، سيتم إنشاء قيمة سمة المسماة ووضعها في قائمة الأعضاء لكائن OBJ2. انظر إلى الصورة:
يمكن العثور على أن OBJ2 لا يزال إشارة إلى النموذج الأولي ، ولم يتم إنشاء مثيلات كائن من نفس حجم النموذج الأولي أثناء العملية. وبهذه الطريقة ، لا تؤدي عمليات الكتابة إلى قدر كبير من تخصيص الذاكرة ، لذلك يبدو استخدام الذاكرة اقتصاديًا. الفرق هو أن OBJ2 (وجميع حالات الكائنات) تحتاج إلى الحفاظ على قائمة الأعضاء. تتبع قائمة الأعضاء هذه قاعدتين: من الضمان الوصول أولاً عند القراءة. إذا لم يتم تحديد أي سمة في الكائن ، فحاول اجتياز سلسلة النموذج الأولي بالكامل للكائن حتى يصبح النموذج الأولي فارغًا أو تم العثور على الخاصية. ستتم مناقشة سلسلة النموذج الأولي لاحقًا. من الواضح ، من بين الأساليب الثلاث ، القراءة هو أفضل أداء. لذلك ، فإن ميراث النموذج الأولي لـ JavaScript هو القراءة. من المؤكد أن الأشخاص المُنشئين الذين يعرفون C ++ سيشوشون بعد قراءة رمز الكائن العلوي. من السهل الفهم بدون الكلمة الرئيسية للفصل ، بعد كل شيء ، هناك كلمات رئيسية للوظيفة ، ولكن الكلمات الرئيسية مختلفة. ولكن ماذا عن المنشئ؟ في الواقع ، لدى JavaScript أيضًا مُنشئين مماثلة ، ولكن يطلق عليهم المُنشئين. عند استخدام المشغل الجديد ، تم استدعاء المُنشئ وهذا ملزم ككائن. على سبيل المثال ، نستخدم الكود التالي
نسخة الكود كما يلي:
var Animal = Animal ("Wangwang") ؛
سوف يكون الحيوان غير محدد. سيقول بعض الناس أنه لا توجد قيمة إرجاع غير محددة بالطبع. ثم إذا قمت بتغيير تعريف كائن الحيوان:
نسخة الكود كما يلي:
وظيفة Animal (اسم) {
this.name = name ؛
إرجاع هذا ؛
}
خمن ما هو الحيوان الآن؟
في هذا الوقت ، أصبح الحيوان نافذة. الفرق هو أنه يوسع النافذة ، بحيث تحتوي هذه النافذة على سمة اسم. هذا لأن هذا الافتراضيات للنافذة ، أي المتغير على المستوى الأعلى دون تحديده. فقط من خلال استدعاء الكلمة الرئيسية الجديدة ، يمكن استدعاء المنشئ بشكل صحيح. لذا ، كيف تجنب الكلمة الرئيسية الجديدة التي يفتقدها الشخص الذي يستخدمها؟ يمكننا إجراء بعض التغييرات الطفيفة:
نسخة الكود كما يلي:
وظيفة Animal (اسم) {
if (! (هذا مثيل الحيوان)) {
إرجاع حيوان جديد (اسم) ؛
}
this.name = name ؛
}
هذا سيكون مضمونة. يحتوي المنشئ أيضًا على استخدام آخر ، يشير إلى الكائن الذي ينتمي إليه المثيل. يمكننا استخدام مثيل الحكم ، ولكن سيعود مثيل OF إلى كائنات الأجداد والكائنات الحقيقية عند الوراثة ، لذلك فهي ليست مناسبة للغاية. عندما يطلق على المنشئ جديدًا ، فإنه يشير إلى الكائن الحالي بشكل افتراضي.
نسخة الكود كما يلي:
console.log (animal.prototype.constructor === Animal) ؛ // حقيقي
يمكننا أن نفكر بشكل مختلف: النموذج الأولي لا قيمة له في بداية الوظيفة ، وقد يكون التنفيذ هو المنطق التالي
// set __proto__ هو عضو مدمج في الوظيفة ، get_prototyoe () هو طريقته
نسخة الكود كما يلي:
var __proto__ = null ؛
وظيفة get_prototype () {
إذا (! __ proto__) {
__proto__ = new Object () ؛
__proto __. constructor = this ؛
}
العودة __proto__ ؛
}
هذه الفائدة هي أنها تتجنب إنشاء مثيل كائن لكل وظيفة معلنة ، وتوفير النفقات العامة. يمكن تعديل المنشئ ، والذي سيتم مناقشته لاحقًا. أعتقد أن الجميع يعلم ما هو الميراث الذي يعتمد على النماذج الأولية ، لذلك لا يظهرون الحد الأدنى من معدل الذكاء.
هناك عدة أنواع من ميراث JS ، وهنا نوعان
1. الطريقة 1 هذه الطريقة هي الأكثر استخدامًا ولديها سلامة أفضل. دعونا نحدد كائنين أولاً
نسخة الكود كما يلي:
وظيفة Animal (اسم) {
this.name = name ؛
}
وظيفة الكلب (العمر) {
this.age = العمر ؛
}
var dog = New Dog (2) ؛
من السهل جدًا بناء الميراث ، قم بتوجيه النموذج الأولي للكائن الطفل إلى مثيل الكائن الأصل (لاحظ أنه مثيل وليس كائنًا)
نسخة الكود كما يلي:
dog.prototype = حيوان جديد ("Wangwang") ؛
في هذا الوقت ، سيكون للكلب سمتين ، الاسم والعمر. وإذا تم استخدام مشغل مثيل الكلب
نسخة الكود كما يلي:
console.log (Dog extryof animal) ؛ // حقيقي
console.log (dog extryof dog) ؛ // خطأ شنيع
هذا يحقق الميراث ، ولكن هناك مشكلة صغيرة
نسخة الكود كما يلي:
console.log (dog.prototype.constructor === Animal) ؛ // حقيقي
console.log (dog.prototype.constructor === Dog) ؛ // خطأ شنيع
يمكنك أن ترى أن الكائن الذي أشار إليه المُنشئ قد تغير ، والذي لا يفي غرضنا. لا يمكننا تحديد من الذي ينتمي إليه مثيل جديد. لذلك ، يمكننا إضافة جملة واحدة:
نسخة الكود كما يلي:
dog.prototype.constructor = الكلب ؛
لنلقي نظرة مرة أخرى:
نسخة الكود كما يلي:
console.log (Dog extryof animal) ؛ // خطأ شنيع
console.log (dog extryof dog) ؛ // حقيقي
منتهي. هذه الطريقة هي رابط في صيانة سلسلة النموذج الأولي ، والتي سيتم شرحها بالتفصيل أدناه. 2. الطريقة 2 لها فوائدها وعيوبها ، لكن العيوب تفوق الفوائد. انظر إلى الكود أولاً
نسخة الكود كما يلي:
<pre name = "code"> function animal (name) {
this.name = name ؛
}
animal.prototype.setName = function (name) {
this.name = name ؛
}
وظيفة الكلب (العمر) {
this.age = العمر ؛
}
dog.prototype = animal.prototype ؛
هذا يتيح نسخ النموذج الأولي.
ميزة هذه الطريقة هي أنها لا تتطلب إنشاء كائنات (مقارنة بالطريقة 1) ، وتوفير الموارد. العيوب واضحة أيضا. بالإضافة إلى نفس المشكلة على النحو الوارد أعلاه ، أي ، يشير المنشئ إلى الكائن الأصل ، يمكنه فقط نسخ الخصائص والأساليب التي أعلنها الكائن الأصل مع النموذج الأولي. وهذا يعني ، في الكود أعلاه ، لا يمكن نسخ سمة اسم الكائن الحي ، ولكن يمكن نسخ طريقة setName. الشيء الأكثر فخرًا هو أن أي تعديل للنموذج الأولي للكائن الطفل سيؤثر على النموذج الأولي للكائن الأصل ، أي أن الحالات التي أعلنها كلا الكائنين ستتأثر. لذلك ، لا ينصح هذه الطريقة.
سلسلة النموذج الأولي
يعرف أي شخص كتب عن الميراث أن الميراث يمكن أن يكون ورثًا من مستويات متعددة. وفي JS ، وهذا يشكل سلسلة النموذج الأولي. ذكرت المقالة أعلاه أيضًا سلسلة النموذج الأولي عدة مرات ، إذن ، ما هي سلسلة النموذج الأولي؟ يجب أن يكون للمثال على الأقل سمة proto تشير إلى النموذج الأولي ، وهو أساس نظام الكائن في JavaScript. ومع ذلك ، فإن هذه الخاصية غير مرئية ، ونحن نسميها "سلسلة النموذج الأولي الداخلي" لتمييزها عن "سلسلة النماذج الأولية المُنشأة" المكونة من النموذج الأولي للمنشئ (أي ما نسميه عادة "سلسلة النموذج الأولي"). دعونا أولاً نبني علاقة ميراث بسيطة وفقًا للرمز أعلاه:
نسخة الكود كما يلي:
وظيفة Animal (اسم) {
this.name = name ؛
}
وظيفة الكلب (العمر) {
this.age = العمر ؛
}
var Animal = New Animal ("Wangwang") ؛
dog.prototype = Animal ؛
var dog = New Dog (2) ؛
كتذكير ، كما ذكرنا سابقًا ، ترث جميع الكائنات كائنات فارغة. لذلك ، نقوم ببناء سلسلة النموذج الأولي:
يمكننا أن نرى أن النموذج الأولي للكائن الفرعي يشير إلى مثيل الكائن الأصل ، مما يشكل سلسلة النموذج الأولي. يعد كائن Proto الداخلي لمثيل الطفل أيضًا مثيلًا يشير إلى الكائن الأصل ، ويشكل سلسلة النموذج الأولي الداخلي. عندما نحتاج إلى العثور على خاصية ، فإن الكود يشبه
نسخة الكود كما يلي:
وظيفة getAttrfromObj (attr ، obj) {
if (typeof (obj) === "كائن") {
var proto = obj ؛
بينما (proto) {
if (proto.hasownproperty (attr)) {
إرجاع proto [attr] ؛
}
proto = proto .__ proto__ ؛
}
}
العودة غير محددة.
}
في هذا المثال ، إذا بحثنا عن سمة الاسم في الكلب ، فسيتم البحث في قائمة الأعضاء في الكلب. بالطبع ، لن يتم العثور عليها ، لأن قائمة الأعضاء الآن للكلاب ليست سوى عمر العنصر. بعد ذلك ، سيستمر في البحث على طول سلسلة النموذج الأولي ، أي المثيل الذي أشار إليه .proto ، أي بالحيوان ، ابحث عن سمة الاسم وإعادتها. إذا كان البحث عبارة عن خاصية غير موجودة ، وعندما لا يمكن العثور عليها في Animal ، فسيستمر البحث مع .proto ، والعثور على كائن فارغ ، ثم استمر في البحث مع .proto ، ويشير .proto للكائن الفارغ إلى NULL ، والبحث عن الخروج.
الحفاظ على سلاسل النموذج الأولي طرحنا سؤالاً عندما تحدثنا عن ميراث النموذج الأولي الآن. عند استخدام الطريقة 10 لبناء الميراث ، يشير مُنشئ مثيل كائن الطفل إلى الكائن الأصل. ميزة ذلك هي أنه يمكننا الوصول إلى سلسلة النموذج الأولي من خلال سمة المنشئ ، والعيوب واضحة أيضًا. كائن يجب أن يشير مثيله إلى نفسه ، أي
نسخة الكود كما يلي:
(جديد OBJ ()). النموذج الأولي.
بعد ذلك ، بعد إعادة كتابة خصائص النموذج الأولي ، لا يشير مُنشئ المثيل الذي تم إنشاؤه بواسطة الكائن الطفل إلى نفسه! هذا يتعارض مع النية الأصلية للمؤسسة. ذكرنا حلًا أعلاه:
نسخة الكود كما يلي:
dog.prototype = حيوان جديد ("Wangwang") ؛
dog.prototype.constructor = الكلب ؛
يبدو أن لا شيء خاطئ. ولكن في الواقع ، فإن هذا يثير مشكلة جديدة لأننا سنجد أنه لا يمكننا العودة إلى سلسلة النموذج الأولي لأننا لا نستطيع العثور على الكائن الأصل ، ولا يمكن الوصول إلى سمة .proto لسلسلة النموذج الأولي الداخلي. لذلك ، يوفر SpidermonKey تحسنا: أضف خاصية تسمى __proto__ إلى أي كائن تم إنشاؤه ، والذي يشير دائمًا إلى النموذج الأولي الذي يستخدمه المنشئ. وبهذه الطريقة ، لن يؤثر أي تعديل على مُنشئ على قيمة __proto__ ، مما يجعله مناسبًا للحفاظ على المنشئ.
ومع ذلك ، هناك مشكلتان أخريان:
__proto__ معاد إعادة الكتابة ، مما يعني أنه لا تزال هناك مخاطر عند استخدامها
__proto__ هي معالجة خاصة لـ SpidermonKey ولا يمكن استخدامها في محركات أخرى (مثل JScript).
هناك طريقة أخرى بالنسبة لنا للحفاظ على خصائص مُنشئ النموذج الأولي وتهيئة خصائص مُنشئ المثيل داخل وظيفة منشئ الفئة الفرعية.
الرمز كما يلي: أعد كتابة كائن الطفل
نسخة الكود كما يلي:
وظيفة الكلب (العمر) {
this.constructor = ediuments.callee ؛
this.age = العمر ؛
}
dog.prototype = حيوان جديد ("Wangwang") ؛
وبهذه الطريقة ، يشير مُنشئ جميع حالات الكائنات الفرعية بشكل صحيح إلى الكائن ، في حين يشير مُنشئ النموذج الأولي إلى الكائن الأصل. على الرغم من أن هذه الطريقة غير فعالة نسبيًا لأنه يجب إعادة كتابة سمة المنشئ في كل مرة يتم فيها إنشاء المثيل ، فلا شك أن هذه الطريقة يمكنها حل التناقضات السابقة بشكل فعال. يأخذ ES5 هذا في الاعتبار ويحل هذه المشكلة بالكامل: يمكن استخدام Object.GetPrototypeoF () في أي وقت للحصول على النموذج الأولي الحقيقي لكائن دون الوصول إلى المُنشئ أو الحفاظ على سلسلة النموذج الأولي الخارجي. لذلك ، كما ذكر في القسم السابق ، يمكننا إعادة كتابته على النحو التالي:
نسخة الكود كما يلي:
وظيفة getAttrfromObj (attr ، obj) {
if (typeof (obj) === "كائن") {
يفعل {
var proto = object.getPrototypeof (dog) ؛
if (proto [attr]) {
إرجاع proto [attr] ؛
}
}
بينما (proto) ؛
}
العودة غير محددة.
}
بالطبع ، لا يمكن استخدام هذه الطريقة إلا في المتصفحات التي تدعم ES5. من أجل التوافق المتخلف ، ما زلنا بحاجة إلى النظر في الطريقة السابقة. طريقة أكثر ملاءمة هي دمج وتغليف هاتين الطريقتين. أعتقد أن القراء جيدون للغاية في هذا ، لذلك لن أكون قبح هنا.