يسمح عامل instanceof
بالتحقق مما إذا كان الكائن ينتمي إلى فئة معينة. كما يأخذ في الاعتبار الميراث.
قد يكون مثل هذا الفحص ضروريًا في كثير من الحالات. على سبيل المثال، يمكن استخدامه لبناء دالة متعددة الأشكال ، تلك التي تتعامل مع الوسائط بشكل مختلف اعتمادًا على نوعها.
بناء الجملة هو:
مثيل obj للفئة
يتم إرجاعه true
إذا كان obj
ينتمي إلى Class
أو إلى فئة ترث منها.
على سبيل المثال:
فئة الأرنب {} دع الأرنب = أرنب جديد ()؛ // هل هو كائن من فئة الأرنب؟ تنبيه (مثيل أرنب للأرنب) ؛ // حقيقي
كما أنه يعمل مع وظائف المنشئ:
// بدلاً من الفصل وظيفة الأرنب () {} تنبيه (جديد أرنب () مثيل للأرنب)؛ // حقيقي
… ومع الفئات المضمنة مثل Array
:
دع arr = [1, 2, 3]; تنبيه (مثيل Array للصفيف) ؛ // حقيقي تنبيه (مثيل كائن)؛ // حقيقي
يرجى ملاحظة أن arr
ينتمي أيضًا إلى فئة Object
. ذلك لأن Array
يرث نموذجيًا من Object
.
عادة، يقوم instanceof
بفحص سلسلة النموذج الأولي للتحقق. يمكننا أيضًا تعيين منطق مخصص في الطريقة الثابتة Symbol.hasInstance
.
تعمل خوارزمية obj instanceof Class
تقريبًا كما يلي:
إذا كانت هناك طريقة ثابتة Symbol.hasInstance
، فما عليك سوى تسميتها: Class[Symbol.hasInstance](obj)
. يجب أن يعود إما true
أو false
، وقد انتهينا. هذه هي الطريقة التي يمكننا بها تخصيص سلوك instanceof
.
على سبيل المثال:
// إعداد مثيل الاختيار الذي يفترض ذلك // أي شيء له خاصية canEat فهو حيوان فئة الحيوان { ثابت [Symbol.hasInstance](obj) { إذا عاد (obj.canEat) صحيحًا؛ } } Let obj = { canEat: true }; تنبيه (مثيل الكائن للحيوان) ؛ // صحيح: يتم استدعاء الحيوان [Symbol.hasInstance] (obj).
معظم الفئات لا تحتوي على Symbol.hasInstance
.hasInstance . في هذه الحالة، يتم استخدام المنطق القياسي: يتحقق obj instanceOf Class
مما إذا كان Class.prototype
يساوي أحد النماذج الأولية في سلسلة النموذج الأولي obj
.
بمعنى آخر، قارن واحدًا تلو الآخر:
obj.__proto__ === Class.prototype؟ obj.__proto__.__proto__ === Class.prototype؟ obj.__proto__.__proto__.__proto__ === Class.prototype؟ ... // إذا كانت أي إجابة صحيحة، فارجع صحيحًا // وإلا، إذا وصلنا إلى نهاية السلسلة، فسيتم إرجاع خطأ
في المثال أعلاه rabbit.__proto__ === Rabbit.prototype
، وهذا يعطي الإجابة على الفور.
وفي حالة الميراث تكون المباراة في الخطوة الثانية:
فئة الحيوان {} فئة الأرنب تمتد الحيوان {} دع الأرنب = أرنب جديد ()؛ تنبيه (مثيل أرنب للحيوان) ؛ // حقيقي // أرنب.__proto__ === Animal.prototype (لا يوجد تطابق) // أرنب.__proto__.__proto__ === Animal.prototype (تطابق!)
فيما يلي الرسم التوضيحي rabbit instanceof Animal
الذي يقارن بنموذج Animal.prototype
:
بالمناسبة، هناك أيضًا طريقة objA.isPrototypeOf(objB)، والتي تُرجع true
إذا كان objA
موجودًا في مكان ما في سلسلة النماذج الأولية لـ objB
. لذلك يمكن إعادة صياغة اختبار obj instanceof Class
على النحو Class.prototype.isPrototypeOf(obj)
.
إنه أمر مضحك، لكن منشئ Class
نفسه لا يشارك في الشيك! فقط سلسلة النماذج الأولية و Class.prototype
هي المهمة.
يمكن أن يؤدي ذلك إلى عواقب مثيرة للاهتمام عند تغيير خاصية prototype
بعد إنشاء الكائن.
مثل هنا:
وظيفة الأرنب () {} دع الأرنب = أرنب جديد ()؛ // تم تغيير النموذج الأولي Rabbit.prototype = {}; // ... لم يعد أرنبًا بعد الآن! تنبيه (مثيل أرنب للأرنب) ؛ // خطأ شنيع
نحن نعلم بالفعل أن الكائنات العادية يتم تحويلها إلى سلسلة كـ [object Object]
:
دع obj = {}; تنبيه (كائن) ؛ // [كائن كائن] تنبيه (obj.toString ())؛ // نفس الشيء
هذا هو تطبيقهم لـ toString
. ولكن هناك ميزة مخفية تجعل toString
في الواقع أقوى بكثير من ذلك. يمكننا استخدامه typeof
ممتد وبديل لـ instanceof
.
يبدو غريبا؟ بالفعل. دعونا إزالة الغموض.
حسب المواصفات، يمكن استخراج toString
المضمن من الكائن وتنفيذه في سياق أي قيمة أخرى. ونتيجته تعتمد على تلك القيمة.
بالنسبة للرقم، سيكون [object Number]
بالنسبة للقيمة المنطقية، ستكون [object Boolean]
بالنسبة null
: [object Null]
undefined
: [object Undefined]
للمصفوفات: [object Array]
…إلخ (قابل للتخصيص).
دعونا نظهر:
// انسخ طريقة toString إلى متغير للراحة Let objectToString = Object.prototype.toString; // ما هو نوع هذا؟ دع arr = []; تنبيه (objectToString.call(arr)); // [مصفوفة الكائنات]
هنا استخدمنا استدعاء كما هو موضح في الفصل الديكور وإعادة التوجيه، استدعاء/تطبيق لتنفيذ الدالة objectToString
في السياق this=arr
.
داخليًا، تقوم خوارزمية toString
بفحص this
وإرجاع النتيجة المقابلة. المزيد من الأمثلة:
Let s = Object.prototype.toString; تنبيه(s.call(123)); // [رقم الكائن] تنبيه(s.call(null)); // [كائن فارغ] تنبيه(s.call(تنبيه)); // [وظيفة الكائن]
يمكن تخصيص سلوك Object toString
باستخدام خاصية كائن خاصة Symbol.toStringTag
.
على سبيل المثال:
السماح للمستخدم = { [Symbol.toStringTag]: "المستخدم" }; تنبيه( {}.toString.call(user)); // [مستخدم الكائن]
بالنسبة لمعظم الكائنات الخاصة بالبيئة، توجد مثل هذه الخاصية. فيما يلي بعض الأمثلة الخاصة بالمتصفح:
// toStringTag للكائن والفئة الخاصة بالبيئة: تنبيه (نافذة [Symbol.toStringTag])؛ // نافذة تنبيه (XMLHttpRequest.prototype[Symbol.toStringTag])؛ // XMLHttpRequest تنبيه( {}.toString.call(window)); // [نافذة الكائن] تنبيه( {}.toString.call(new XMLHttpRequest()) ); // [كائن XMLHttpRequest]
كما ترون، فإن النتيجة هي بالضبط Symbol.toStringTag
(إذا كان موجودًا)، ملفوفًا في [object ...]
.
في النهاية لدينا "typeof onroiders" الذي لا يعمل فقط مع أنواع البيانات البدائية، ولكن أيضًا مع الكائنات المضمنة وحتى التي يمكن تخصيصها.
يمكننا استخدام {}.toString.call
بدلاً من instanceof
الكائنات المضمنة عندما نريد الحصول على النوع كسلسلة بدلاً من مجرد التحقق.
دعونا نلخص طرق التحقق من النوع التي نعرفها:
يعمل ل | يعود | |
---|---|---|
typeof | البدائيون | خيط |
{}.toString | البدائيات، والكائنات المضمنة، والكائنات ذات Symbol.toStringTag | خيط |
instanceof | أشياء | صحيح / خطأ |
كما نرى، {}.toString
هو من الناحية الفنية typeof
.
instanceof
العامل يتألق حقًا عندما نعمل مع تسلسل هرمي للفصل ونريد التحقق من الفصل مع مراعاة الميراث.
الأهمية: 5
في الكود أدناه، لماذا يُرجع instanceof
true
؟ يمكننا أن نرى بسهولة أن a
لم يتم إنشاؤه بواسطة B()
.
الدالة أ () {} الدالة ب () {} A.prototype = B.prototype = {}; دع a = جديد A(); تنبيه (مثال B)؛ // حقيقي
نعم، يبدو غريبا حقا.
لكن instanceof
لا يهتم بالوظيفة، بل prototype
الخاص بها، والذي يتطابق مع سلسلة النموذج الأولي.
وهنا a.__proto__ == B.prototype
، لذا يُرجع instanceof
true
.
لذا، وفقًا لمنطق instanceof
، يحدد prototype
النوع، وليس وظيفة المنشئ.