يُتيح لنا المُعامل instanceof
(أهو سيرورة من) فحص هل الكائن ينتمي إلى الصنف الفلاني؟ كما يأخذ الوراثة في الحسبان عند الفحص.
توجد حالات عديدة يكون فيها هذا الفحص ضروريًا. **سنستعمله هنا لصناعة دالة **، أي دالة تغيّر تعاملها حسب نوع الوسطاء الممرّرة لها.
معامل instanceof
صياغته هي:
obj instanceof Class
يُعيد المُعامل قيمة true
لو كان الكائن obj
ينتمي إلى الصنف Class
أو أيّ صنف يرثه.
مثال:
class Rabbit {} let rabbit = new Rabbit(); // هل هو كائن من الصنف Rabbit؟ alert( rabbit instanceof Rabbit ); // true نعم
كما يعمل مع الدوال البانية:
// بدل استعمال class function Rabbit() {} alert( new Rabbit() instanceof Rabbit ); // true نعم
كما والأصناف المضمّنة مثل المصفوفات Array
:
let arr = [1, 2, 3]; alert( arr instanceof Array ); // true نعم alert( arr instanceof Object ); // true نعم
لاحظ كيف أنّ الكائن arr
ينتمي إلى صنف الكائنات Object
أيضًا، إذ ترث المصفوفات -عبر prototype- الكائناتَ.
عادةً ما يتحقّق المُعامل instanceof
من سلسلة prototype عند الفحص. كما يمكننا وضع المنطق الذي نريد لكلّ صنف في التابِع الثابت Symbol.hasInstance
.
تعمل خوارزمية obj instanceof Class
بهذه الطريقة تقريبًا:
-
لو وجدت التابِع الثابت
Symbol.hasInstance
تستدعيه وينتهي الأمر:Class[Symbol.hasInstance](obj)
. يُعيد التابِع إمّاtrue
وإمّاfalse
. هكذا نخصّص سلوك المُعاملinstanceof
.مثال:
// ضبط instanceOf للتحقق من الافتراض القائل // بأن كل شيء يملك الخاصية canEat هو حيوان class Animal { static [Symbol.hasInstance](obj) { if (obj.canEat) return true; } } let obj = { canEat: true }; alert(obj instanceof Animal); // true // تستدعى Animal[Symbol.hasInstance](obj)
-
ليس لأغلب الأصناف التابِع
Symbol.hasInstance
. في هذه الحالة تستعمل المنطق العادي: يفحصobj instanceOf Class
لو كان كائنClass.prototype
مساويًا لأحد كائنات prototype في سلسلة كائنات prototype للكائنobj
.وبعبارة أخرى ، وازن بينهم واحدًا تلو الآخر:
obj.__proto__ === Class.prototype? obj.__proto__.__proto__ === Class.prototype? obj.__proto__.__proto__.__proto__ === Class.prototype? ... // لو كانت إجابة أيًا منها true، فتُعيد true // وإلّا متى وصلت نهاية السلسلة أعادت false
في المثال أعلاه نرى
rabbit.__proto__ === Rabbit.prototype
، بذلك تُعطينا الجواب مباشرةً.أمّا لو كنّا في حالة وراثة، فستتوقّف عملية المطابقة عند الخطوة الثانية:
class Animal {} class Rabbit extends Animal {} let rabbit = new Rabbit(); alert(rabbit instanceof Animal); // (هنا) نعم // rabbit.__proto__ === Rabbit.prototype // rabbit.__proto__.__proto__ === Animal.prototype (تطابق!)
إليك صورة توضّح طريقة موازنة rabbit instanceof Animal
مع Animal.prototype
:
كما وهناك أيضًا التابِع objA.isPrototypeOf(objB)، وهو يُعيد true
لو كان الكائن objA
في مكان داخل سلسلة prototype للكائن objB
. يعني أنّنا نستطيع كتابة الفحص هذا obj instanceof Class
هكذا: Class.prototype.isPrototypeOf(obj)
.
الأمر مضحك إذ أنّ باني الصنف Class
نفسه ليس لديه أيّ كلمة عن هذا الفحص! المهم هو سلسلة prototype وكائن Class.prototype
فقط.
يمكن أن يؤدّي هذا إلى عواقب مثيرة متى تغيّرت خاصية prototype
للكائن بعد إنشائه. طالع:
function Rabbit() {} let rabbit = new Rabbit(); // غيّرنا كائن prototype Rabbit.prototype = {}; // ...لم يعد أرنبًا بعد الآن! alert( rabbit instanceof Rabbit ); // false لا
التابع Object.prototype.toString للأنواع
نعلم بأنّ الكائنات العادية حين تتحوّل إلى سلاسل نصية تصير [object Object]
:
let obj = {}; alert(obj); // [object Object] alert(obj.toString()); // كما أعلاه
يعتمد هذا على طريقة توفيرهم لتنفيذ التابِع toString
. ولكن هناك ميزة مخفيّة تجعل هذا التابِع أكثر فائدة بكثير ممّا هو عليه، أن نستعمله على أنّه مُعامل typeof
موسّع المزايا، أو بديلًا عن التابِع toString
.
تبدو غريبة؟ أليس كذلك؟ لنُزل الغموض.
حسب المواصفة، فيمكننا استخراج التابِع toString
المضمّن من الكائن وتنفيذه في سياق أيّ قيمة أخرى نريد، وسيكون ناتجه حسب تلك القيمة.
-
لو كان عددًا، فسيكون
[object Number]
-
لو كان قيمة منطقية، فسيكون
[object Boolean]
-
لو كان
null
: [object Null]
-
لو كان
undefined
: [object Undefined]
-
لو كانت مصفوفة:
[object Array]
- …إلى آخره (ويمكننا تخصيص ذلك).
هيًا نوضّح:
// ننسخ التابِع toString إلى متغير ليسهل عملنا let objectToString = Object.prototype.toString; // ما هذا النوع؟ let arr = []; alert( objectToString.call(arr) ); // [object Array] مصفوفة!
استعملنا هنا call كما وضّحنا في درس ”المزخرفات والتمرير“ لتنفيذ الدالة objectToString
بسياق this=arr
.
تفحص خوارزمية toString
داخليًا قيمة this
وتُعيد الناتج الموافق لها. إليك أمثلة أخرى:
let s = Object.prototype.toString; alert( s.call(123) ); // [object Number] alert( s.call(null) ); // [object Null] alert( s.call(alert) ); // [object Function]
Symbol.toStringTag
يمكننا تخصيص سلوك التابِع toString
للكائنات باستعمال خاصية الكائنات Symbol.toStringTag
الفريدة.
مثال:
let user = { [Symbol.toStringTag]: "User" // مستخدم }; alert( {}.toString.call(user) ); // [object User] // لاحظ
لأغلب الكائنات الخاصّة بالبيئات خاصية مثل هذه. إليك بعض الأمثلة من المتصفّحات مثلًا:
// تابِع toStringTag للكائنات والأصناف الخاصّة بالمتصفّحات: alert( window[Symbol.toStringTag]); // window alert( XMLHttpRequest.prototype[Symbol.toStringTag] ); // XMLHttpRequest alert( {}.toString.call(window) ); // [object Window] alert( {}.toString.call(new XMLHttpRequest()) ); // [object XMLHttpRequest]
كما نرى فالناتج هو تمامًا ما يقوله Symbol.toStringTag
(لو وُجد) بغلاف [object ...]
.
في النهاية نجد أن لدينا "نوع من المنشطات" لا تعمل فقط على الأنواع الأولية للبيانات بل وحتى الكائنات المضمنة في اللغة ويمكننا تخصيصها أيضًا.
يمكننا استخدام {}.toString.call
بدلًا من instanceof
للكائنات المضمنة في اللغة عندما نريد الحصول على نوع البيانات كسلسلة بدلًا من التحقق منها.
خلاصة
لنلخّص ما نعرف عن التوابِع التي تفحص الأنواع:
يعمل على | يُعيد | |
---|---|---|
typeof
|
الأنواع الأولية | سلسلة نصية |
{}.toString
|
الأنواع الأولية والكائنات المضمّنة والكائنات التي لها Symbol.toStringTag
|
سلسلة نصية |
instanceof
|
الكائنات | true/false |
كما نرى فعبارة {}.toString
هي تقنيًا typeof
ولكن ”متقدّمة أكثر“.
بل إن التابع instanceof
يؤدي دور مميّز عندما نتعامل مع تسلسل هرمي للأصناف ونريد التحقق من صنفٍ ما مع مراعاة الوراثة.
تمارين
instnaceof غريب عجيب
الأهمية: 5
في الشيفرة أسفله، لماذا يُعيد instanceof
القيمة true
. يتّضح جليًا بأنّ B()
لم يُنشِئ a
.
function A() {} function B() {} A.prototype = B.prototype = {}; let a = new A(); // هنا alert( a instanceof B ); // true
الحل
أجل، غريب عجيب حقًا.
ولكن كما نعرف فلا يكترث المُعامل instanceof
بالدالة، بل بكائن prototype لها حيث تُطابقه مع غيره في سلسلة prototype.
وهنا نجد a.__proto__ == B.prototype
، بذلك يُعيد instanceof
القيمة true
.
إذًا فحسب منطق instanceof
، كائن prototype
هو الذي يُعرّف النوع وليس الدالة البانية.
ترجمة -وبتصرف- للفصل Class checking: "instanceof" من كتاب The JavaScript language
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.