تُستخدم خاصية "prototype"
على نطاق واسع في جوهر JavaScript نفسها. تستخدمه جميع وظائف المُنشئ المضمنة.
سنلقي نظرة أولاً على التفاصيل، ثم سنلقي نظرة على كيفية استخدامها لإضافة إمكانات جديدة إلى الكائنات المضمنة.
لنفترض أننا أخرجنا كائنًا فارغًا:
دع obj = {}; تنبيه (الكائن)؛ // "[كائن كائن]"؟
أين هو الكود الذي ينشئ السلسلة "[object Object]"
؟ هذه طريقة toString
مدمجة، لكن أين هي؟ obj
فارغ!
…لكن التدوين القصير obj = {}
هو نفسه obj = new Object()
، حيث Object
عبارة عن وظيفة منشئ كائن مضمنة، مع prototype
خاص بها يشير إلى كائن ضخم باستخدام toString
وطرق أخرى.
إليك ما يحدث:
عند استدعاء new Object()
(أو إنشاء كائن حرفي {...}
)، يتم تعيين [[Prototype]]
منه إلى Object.prototype
وفقًا للقاعدة التي ناقشناها في الفصل السابق:
لذلك عندما يتم استدعاء obj.toString()
يتم أخذ الطريقة من Object.prototype
.
يمكننا التحقق من ذلك مثل هذا:
دع obj = {}; تنبيه(obj.__proto__ === Object.prototype); // حقيقي تنبيه (obj.toString === obj.__proto__.toString); //حقيقي تنبيه (obj.toString === Object.prototype.toString)؛ //حقيقي
يرجى ملاحظة أنه لم يعد هناك [[Prototype]]
في السلسلة الموجودة أعلى Object.prototype
:
تنبيه(Object.prototype.__proto__); // باطل
الكائنات المضمنة الأخرى مثل Array
و Date
و Function
وغيرها تحتفظ أيضًا بالطرق في النماذج الأولية.
على سبيل المثال، عندما نقوم بإنشاء مصفوفة [1, 2, 3]
، يتم استخدام مُنشئ new Array()
داخليًا. لذلك يصبح Array.prototype
هو النموذج الأولي ويوفر الطرق. هذا فعال جدًا في الذاكرة.
وفقًا للمواصفات، تحتوي جميع النماذج الأولية المضمنة على Object.prototype
في الأعلى. ولهذا يقول بعض الناس: "كل شيء يرث من الأشياء".
هذه هي الصورة العامة (لملاءمة 3 عناصر مدمجة):
دعونا نتحقق من النماذج الأولية يدويًا:
دع arr = [1, 2, 3]; // يرث من Array.prototype؟ تنبيه( arr.__proto__ === Array.prototype ); // حقيقي // ثم من Object.prototype؟ تنبيه( arr.__proto__.__proto__ === Object.prototype ); // حقيقي // وفارغة في الأعلى. تنبيه( arr.__proto__.__proto__.__proto__ ); // باطل
قد تتداخل بعض الأساليب في النماذج الأولية، على سبيل المثال، يحتوي Array.prototype
على toString
خاصة به تسرد عناصر مفصولة بفواصل:
دع آر = [1، 2، 3] تنبيه(arr); // 1,2,3 <-- نتيجة Array.prototype.toString
كما رأينا من قبل، يحتوي Object.prototype
على toString
أيضًا، ولكن Array.prototype
أقرب في السلسلة، لذلك يتم استخدام متغير المصفوفة.
تعرض الأدوات الموجودة في المتصفح مثل وحدة تحكم مطور Chrome أيضًا الوراثة (قد يلزم استخدام console.dir
للكائنات المضمنة):
تعمل الكائنات المضمنة الأخرى أيضًا بنفس الطريقة. حتى الدوال – هي كائنات من مُنشئ Function
المضمنة، وأساليبها ( call
/ apply
وغيرها) مأخوذة من Function.prototype
. الوظائف لها toString
الخاصة بها أيضًا.
الدالة و () {} تنبيه (f.__proto__ == Function.prototype); // حقيقي تنبيه(f.__proto__.__proto__ == Object.prototype); // صحيح، يرث من الكائنات
يحدث الشيء الأكثر تعقيدًا مع السلاسل والأرقام والمنطقيات.
وكما نتذكر، فهي ليست أشياء. ولكن إذا حاولنا الوصول إلى خصائصها، فسيتم إنشاء كائنات الغلاف المؤقتة باستخدام المُنشئات المضمنة String
و Number
و Boolean
. أنها توفر الأساليب وتختفي.
يتم إنشاء هذه الكائنات بشكل غير مرئي لنا وتقوم معظم المحركات بتحسينها، لكن المواصفات تصفها بهذه الطريقة تمامًا. توجد أيضًا طرق هذه الكائنات في نماذج أولية، وهي متاحة كـ String.prototype
و Number.prototype
و Boolean.prototype
.
القيم null
undefined
لا تحتوي على أغلفة كائنات
القيم الخاصة null
undefined
تقف منفصلة. ليس لديهم أي أغلفة للكائنات، لذلك لا تتوفر لهم الأساليب والخصائص. ولا توجد نماذج أولية مقابلة أيضًا.
يمكن تعديل النماذج الأولية الأصلية. على سبيل المثال، إذا أضفنا تابعًا إلى String.prototype
، فإنه يصبح متاحًا لجميع السلاسل النصية:
String.prototype.show = function() { تنبيه(هذا); }; "بوم!".show(); // بوم!
أثناء عملية التطوير، قد تكون لدينا أفكار لطرق مدمجة جديدة نرغب في الحصول عليها، وقد نرغب في إضافتها إلى النماذج الأولية الأصلية. لكن هذه فكرة سيئة بشكل عام.
مهم:
النماذج الأولية عالمية، لذلك من السهل حدوث صراع. إذا أضافت مكتبتان طريقة String.prototype.show
، فستقوم إحداهما بالكتابة فوق طريقة الأخرى.
لذا، بشكل عام، يعد تعديل النموذج الأولي فكرة سيئة.
في البرمجة الحديثة، هناك حالة واحدة فقط تتم فيها الموافقة على تعديل النماذج الأولية الأصلية. هذا هو polyfilling.
Polyfilling هو مصطلح يشير إلى إنشاء بديل لطريقة موجودة في مواصفات JavaScript، ولكنها غير مدعومة حتى الآن بواسطة محرك JavaScript معين.
يمكننا بعد ذلك تنفيذه يدويًا وتعبئة النموذج الأولي المدمج به.
على سبيل المثال:
إذا (!String.prototype.repeat) {// إذا لم يكن هناك مثل هذه الطريقة // أضفه إلى النموذج الأولي String.prototype.repeat = function(n) { // كرر السلسلة مرات n // في الواقع، يجب أن يكون الكود أكثر تعقيدًا من ذلك قليلًا // (الخوارزمية الكاملة موجودة في المواصفات) // ولكن حتى الحشو المتعدد غير الكامل غالبًا ما يُعتبر جيدًا بدرجة كافية إرجاع مصفوفة جديدة (n + 1).join(this); }; } تنبيه("لا".كرر(3)); // لالا
في فصل الديكور وإعادة التوجيه، اتصل/قدم، تحدثنا عن طريقة الاقتراض.
وذلك عندما نأخذ طريقة من كائن وننسخها إلى كائن آخر.
غالبًا ما يتم استعارة بعض أساليب النماذج الأولية الأصلية.
على سبيل المثال، إذا كنا نصنع كائنًا يشبه المصفوفة، فقد نرغب في نسخ بعض توابع Array
إليه.
على سبيل المثال
دع الكائن = { 0: "مرحبا"، 1: "العالم!"، الطول: 2, }; obj.join = Array.prototype.join; تنبيه (obj.join(',') ); // مرحبا بالعالم!
إنه يعمل لأن الخوارزمية الداخلية لطريقة join
المضمنة تهتم فقط بالفهارس الصحيحة وخاصية length
. لا يتحقق مما إذا كان الكائن عبارة عن مصفوفة بالفعل. العديد من الأساليب المضمنة هي من هذا القبيل.
هناك احتمال آخر وهو الوراثة عن طريق ضبط obj.__proto__
على Array.prototype
، بحيث تكون جميع أساليب Array
متاحة تلقائيًا في obj
.
لكن هذا مستحيل إذا ورث obj
بالفعل من كائن آخر. تذكر أننا لا نستطيع أن نرث إلا من كائن واحد في كل مرة.
تتميز طرق الاقتراض بالمرونة، فهي تسمح بمزج الوظائف من كائنات مختلفة إذا لزم الأمر.
تتبع جميع الكائنات المضمنة نفس النمط:
يتم تخزين الأساليب في النموذج الأولي ( Array.prototype
، Object.prototype
، Date.prototype
، وما إلى ذلك)
يقوم الكائن نفسه بتخزين البيانات فقط (عناصر المصفوفة وخصائص الكائن والتاريخ)
تقوم البدائيات أيضًا بتخزين الأساليب في النماذج الأولية لكائنات الغلاف: Number.prototype
و String.prototype
و Boolean.prototype
. فقط undefined
null
لا يحتويان على كائنات مجمعة
يمكن تعديل النماذج الأولية المضمنة أو ملؤها بطرق جديدة. لكن لا ينصح بتغييرها. ربما تكون الحالة الوحيدة المسموح بها هي عندما نضيف معيارًا جديدًا، ولكنه غير مدعوم حتى الآن بواسطة محرك JavaScript
الأهمية: 5
أضف إلى النموذج الأولي لجميع الوظائف الطريقة defer(ms)
، التي تقوم بتشغيل الوظيفة بعد ms
ثانية.
بعد القيام بذلك، يجب أن يعمل هذا الكود:
الدالة و() { تنبيه("مرحبا!"); } f.defer(1000); // يظهر "مرحبا!" بعد 1 ثانية
Function.prototype.defer = وظيفة (مللي ثانية) { setTimeout(this, ms); }; الدالة و() { تنبيه("مرحبا!"); } f.defer(1000); // يظهر "مرحبا!" بعد 1 ثانية
الأهمية: 4
أضف إلى النموذج الأولي لجميع الوظائف الطريقة defer(ms)
، التي تقوم بإرجاع غلاف، وتأخير المكالمة بمقدار مللي ms
.
فيما يلي مثال لكيفية العمل:
الدالة و(أ، ب) { تنبيه (أ + ب)؛ } f.defer(1000)(1, 2); // يظهر 3 بعد ثانية واحدة
يرجى ملاحظة أنه يجب تمرير الوسائط إلى الوظيفة الأصلية.
Function.prototype.defer = وظيفة (مللي ثانية) { دع و = هذا؛ وظيفة الإرجاع (...الوسائط) { setTimeout(() => f.apply(this, args), ms); } }; // التحقق من ذلك الدالة و(أ، ب) { تنبيه (أ + ب)؛ } f.defer(1000)(1, 2); // يظهر 3 بعد 1 ثانية
يرجى ملاحظة: أننا نستخدم this
في f.apply
لجعل الزخرفة لدينا تعمل مع طرق الكائنات.
لذا، إذا تم استدعاء وظيفة التغليف كطريقة كائن، فسيتم تمرير this
إلى الطريقة الأصلية f
.
Function.prototype.defer = وظيفة (مللي ثانية) { دع و = هذا؛ وظيفة الإرجاع (...الوسائط) { setTimeout(() => f.apply(this, args), ms); } }; السماح للمستخدم = { الاسم: "جون"، قل مرحبا () { تنبيه(هذا.اسم); } } user.sayHi = user.sayHi.defer(1000); user.sayHi();