تذكر أنه يمكن إنشاء كائنات جديدة باستخدام دالة منشئة، مثل new F()
.
إذا كان F.prototype
كائنًا، فإن العامل new
يستخدمه لتعيين [[Prototype]]
للكائن الجديد.
يرجى الملاحظة:
كان لجافا سكريبت وراثة نموذجية منذ البداية. وكانت واحدة من السمات الأساسية للغة.
لكن في العصور القديمة لم يكن هناك وصول مباشر إليها. الشيء الوحيد الذي نجح بشكل موثوق هو خاصية "prototype"
لوظيفة المنشئ، الموضحة في هذا الفصل. لذلك هناك العديد من البرامج النصية التي لا تزال تستخدمها.
يرجى ملاحظة أن F.prototype
هنا تعني خاصية عادية تسمى "prototype"
في F
. يبدو الأمر مشابهًا لمصطلح "النموذج الأولي"، لكننا هنا نعني حقًا خاصية عادية بهذا الاسم.
إليك المثال:
دع الحيوان = { يأكل: صحيح }; وظيفة الأرنب (الاسم) { this.name = name; } Rabbit.prototype = حيوان؛ دع الأرنب = أرنب جديد("الأرنب الأبيض"); // أرنب.__proto__ == حيوان تنبيه (أرنب يأكل) ؛ // حقيقي
إعداد Rabbit.prototype = animal
ينص حرفيًا على ما يلي: "عندما يتم إنشاء new Rabbit
، قم بتعيين [[Prototype]]
الخاص به إلى animal
".
وهذه هي الصورة الناتجة:
في الصورة "prototype"
سهم أفقي يعني خاصية منتظمة، و [[Prototype]]
سهم عمودي يعني وراثة rabbit
من animal
.
F.prototype
يستخدم فقط في وقت new F
تُستخدم خاصية F.prototype
فقط عند استدعاء new F
، فهي تقوم بتعيين [[Prototype]]
للكائن الجديد.
إذا تغيرت خاصية F.prototype
بعد الإنشاء ( F.prototype = <another object>
)، فإن الكائنات الجديدة التي تم إنشاؤها بواسطة new F
سيكون لها كائن آخر مثل [[Prototype]]
، لكن الكائنات الموجودة بالفعل ستحتفظ بالكائن القديم.
كل وظيفة لها خاصية "prototype"
حتى لو لم نوفرها.
"prototype"
الافتراضي هو كائن يحتوي على constructor
الخاصية الوحيد الذي يشير إلى الوظيفة نفسها.
مثله:
وظيفة الأرنب () {} /* النموذج الافتراضي Rabbit.prototype = { المُنشئ: أرنب }; */
يمكننا التحقق من ذلك:
وظيفة الأرنب () {} // بشكل افتراضي: // Rabbit.prototype = { المُنشئ: أرنب } تنبيه (Rabbit.prototype.constructor == Rabbit)؛ // حقيقي
بطبيعة الحال، إذا لم نفعل شيئًا، ستكون خاصية constructor
متاحة لجميع الأرانب من خلال [[Prototype]]
:
وظيفة الأرنب () {} // بشكل افتراضي: // Rabbit.prototype = { المُنشئ: أرنب } دع الأرنب = أرنب جديد ()؛ // يرث من {المنشئ: أرنب} تنبيه (rabbit.constructor == أرنب)؛ // صحيح (من النموذج الأولي)
يمكننا استخدام خاصية constructor
لإنشاء كائن جديد باستخدام نفس المنشئ الموجود.
مثل هنا:
وظيفة الأرنب (الاسم) { this.name = name; تنبيه (الاسم)؛ } دع الأرنب = أرنب جديد("الأرنب الأبيض"); دع أرنب2 = أرنب جديد.constructor("الأرنب الأسود");
يكون هذا مفيدًا عندما يكون لدينا كائن، ولا نعرف أي مُنشئ تم استخدامه له (على سبيل المثال، يأتي من مكتبة تابعة لجهة خارجية)، ونحتاج إلى إنشاء كائن آخر من نفس النوع.
ولكن ربما يكون الشيء الأكثر أهمية في "constructor"
هو أنه ...
…لا تضمن JavaScript نفسها القيمة "constructor"
الصحيحة.
نعم، إنه موجود في "prototype"
الافتراضي للوظائف، ولكن هذا كل شيء. وما سيحدث لاحقًا – يقع على عاتقنا تمامًا.
على وجه الخصوص، إذا استبدلنا النموذج الأولي الافتراضي ككل، فلن يكون هناك "constructor"
فيه.
على سبيل المثال:
وظيفة الأرنب () {} أرنب.النموذج الأولي = { القفزات: صحيح }; دع الأرنب = أرنب جديد ()؛ تنبيه (rabbit.constructor === أرنب)؛ // خطأ شنيع
لذلك، للحفاظ على "constructor"
الصحيح، يمكننا اختيار إضافة/إزالة الخصائص إلى "prototype"
الافتراضي بدلاً من الكتابة فوقه ككل:
وظيفة الأرنب () {} // عدم الكتابة فوق Rabbit.prototype تمامًا // فقط أضف إليها Rabbit.prototype.jumps = صحيح // يتم الحفاظ على Rabbit.prototype.constructor الافتراضي
أو، بدلاً من ذلك، أعد إنشاء خاصية constructor
يدويًا:
أرنب.النموذج الأولي = { القفزات: صحيح، البناء: أرنب }; // الآن المُنشئ صحيح أيضًا، لأننا أضفناه
في هذا الفصل وصفنا بإيجاز طريقة إعداد [[Prototype]]
للكائنات التي تم إنشاؤها عبر وظيفة المنشئ. وسنرى لاحقًا أنماط برمجة أكثر تقدمًا تعتمد عليها.
كل شيء بسيط للغاية، فقط بعض الملاحظات لتوضيح الأمور:
الخاصية F.prototype
(لا تخلط بينها وبين [[Prototype]]
) تقوم بتعيين [[Prototype]]
لكائنات جديدة عند استدعاء new F()
.
يجب أن تكون قيمة F.prototype
إما كائنًا أو null
: لن تعمل القيم الأخرى.
خاصية "prototype"
لها مثل هذا التأثير الخاص فقط عند تعيينها على دالة منشئة، واستدعائها باستخدام new
.
في الكائنات العادية، لا يعد prototype
شيئًا مميزًا:
السماح للمستخدم = { الاسم: "جون"، النموذج الأولي: "Bla-bla" // لا يوجد سحر على الإطلاق };
بشكل افتراضي، تحتوي جميع الوظائف على F.prototype = { constructor: F }
، حتى نتمكن من الحصول على مُنشئ الكائن عن طريق الوصول إلى خاصية "constructor"
الخاصة به.
الأهمية: 5
في الكود أدناه نقوم بإنشاء new Rabbit
، ثم نحاول تعديل النموذج الأولي الخاص به.
في البداية لدينا هذا الكود:
وظيفة الأرنب () {} أرنب.النموذج الأولي = { يأكل: صحيح }; دع الأرنب = أرنب جديد ()؛ تنبيه (أرنب يأكل) ؛ // حقيقي
أضفنا سلسلة أخرى (مؤكدة). ماذا سيظهر alert
الآن؟
وظيفة الأرنب () {} أرنب.النموذج الأولي = { يأكل: صحيح }; دع الأرنب = أرنب جديد ()؛ Rabbit.prototype = {}; تنبيه (أرنب يأكل) ؛ // ؟
…وإذا كان الكود هكذا (تم استبدال سطر واحد)؟
وظيفة الأرنب () {} أرنب.النموذج الأولي = { يأكل: صحيح }; دع الأرنب = أرنب جديد ()؛ Rabbit.prototype.eats = false; تنبيه (أرنب يأكل) ؛ // ؟
وهكذا (استبدلت سطراً واحداً)؟
وظيفة الأرنب () {} أرنب.النموذج الأولي = { يأكل: صحيح }; دع الأرنب = أرنب جديد ()؛ حذف أرنب يأكل؛ تنبيه (أرنب يأكل) ؛ // ؟
البديل الأخير:
وظيفة الأرنب () {} أرنب.النموذج الأولي = { يأكل: صحيح }; دع الأرنب = أرنب جديد ()؛ حذف Rabbit.prototype.eats؛ تنبيه (أرنب يأكل) ؛ // ؟
الإجابات:
true
.
يؤدي تعيين Rabbit.prototype
إلى إعداد [[Prototype]]
للكائنات الجديدة، ولكنه لا يؤثر على الكائنات الموجودة.
false
.
يتم تعيين الكائنات حسب المرجع. الكائن من Rabbit.prototype
غير مكرر، فهو لا يزال كائنًا واحدًا تمت الإشارة إليه بواسطة Rabbit.prototype
و [[Prototype]]
من rabbit
.
لذلك عندما نغير محتواه من خلال مرجع واحد، فإنه يظهر من خلال المرجع الآخر.
true
.
يتم تطبيق كافة عمليات delete
مباشرة على الكائن. يحاول delete rabbit.eats
هنا إزالة خاصية eats
من rabbit
، لكنه لا يمتلكها. وبالتالي فإن العملية لن يكون لها أي تأثير.
undefined
.
تم حذف الخاصية eats
من النموذج الأولي، ولم تعد موجودة.
الأهمية: 5
تخيل أن لدينا كائنًا عشوائيًا obj
، تم إنشاؤه بواسطة دالة منشئة - لا نعرف أي منها، ولكننا نرغب في إنشاء كائن جديد باستخدامه.
هل يمكننا أن نفعل ذلك من هذا القبيل؟
دع obj2 = new obj.constructor();
أعط مثالاً على وظيفة منشئة لـ obj
والتي تتيح لهذا الكود العمل بشكل صحيح. ومثال الذي يجعله يعمل بشكل خاطئ.
يمكننا استخدام هذا النهج إذا كنا متأكدين من أن خاصية "constructor"
لها القيمة الصحيحة.
على سبيل المثال، إذا لم نلمس "prototype"
الافتراضي، فإن هذا الرمز يعمل بالتأكيد:
وظيفة المستخدم (الاسم) { this.name = name; } اسمح للمستخدم = مستخدم جديد('جون'); Let user2 = new user.constructor('Pete'); تنبيه (اسم المستخدم 2) ؛ // بيت (عمل!)
لقد نجح الأمر، لأن User.prototype.constructor == User
.
…ولكن إذا قام شخص ما، إذا جاز التعبير، بالكتابة فوق User.prototype
ونسي إعادة إنشاء constructor
للإشارة إلى User
، فسوف يفشل.
على سبيل المثال:
وظيفة المستخدم (الاسم) { this.name = name; } User.prototype = {}; // (*) اسمح للمستخدم = مستخدم جديد('جون'); Let user2 = new user.constructor('Pete'); تنبيه (اسم المستخدم 2) ؛ // غير محدد
لماذا user2.name
undefined
؟
إليك كيفية عمل new user.constructor('Pete')
:
أولاً، يبحث عن constructor
في user
. لا شئ.
ثم يتبع سلسلة النموذج الأولي. النموذج الأولي user
هو User.prototype
، كما أنه لا يحتوي على constructor
(لأننا "نسينا" ضبطه بشكل صحيح!).
بالمضي قدمًا في السلسلة، يعد User.prototype
كائنًا عاديًا، ونموذجه الأولي هو Object.prototype
المدمج.
أخيرًا، بالنسبة لـ Object.prototype
المضمن، هناك Object.prototype.constructor == Object
مضمن. لذلك يتم استخدامه.
أخيرًا، في النهاية، let user2 = new Object('Pete')
.
ربما، هذا ليس ما نريده. نود إنشاء new User
، وليس new Object
. هذه هي نتيجة constructor
المفقود.
(فقط في حالة فضولك، فإن استدعاء new Object(...)
يحول وسيطته إلى كائن. وهذا شيء نظري، في الممارسة العملية لا أحد يستدعي new Object
بقيمة، ولا نستخدم new Object
بشكل عام لصنع الأشياء على الإطلاق).