عملگر instanceOf
به ما این امکان را میدهد که بررسی کنیم یک شیء به کلاسی مشخص تعلق دارد یا خیر. این عملگر ارثبری را هم محسوب میکند.
چنین بررسیای ممکن است در موارد بسیاری ضروری باشد. برای مثال، میتوانیم برای ساخت یک تابع چندریخت (polymorphic) از آن استفاده کنیم، تابعی که بر اساس نوع آرگومانها با آنها به صورت متفاوت رفتار میکند.
عملگر instanceof
سینتکس اینگونه است:
obj instanceof Class
اگر obj
به Class
یا کلاسی که از آن ارثبری میکند تعلق داشته باشد، این عملگر مقدار true
را برمیگرداند.
برای مثال:
class Rabbit {}
let rabbit = new Rabbit();
// است؟ Rabbit آیا شیءای از کلاس
alert( rabbit instanceof Rabbit ); // true
با تابعهای سازنده هم کار میکند:
// به جای کلاس
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
تعلق دارد. به این دلیل که Array
به صورت پروتوتایپی از Object
ارثبری میکند.
معمولا، instanceof
زنجیره پروتوتایپ را بررسی میکند. ما هم میتوانیم یک منطق سفارشی در متد ایستای Symbol.hasInstance
ایجاد کنیم.
الگوریتم obj instanceof Class
تقریبا اینگونه عمل میکند:
-
اگر متد ایستای
Symbol.hasInstance
وجود داشته باشد، سپس آن را فراخوانی کن:Class[Symbol.hasInstance](obj)
. این متد بایدtrue
یاfalse
را برگرداند و کار تمام است. ما اینگونه رفتارinstanceof
را شخصیسازی میکنیم.برای مثال:
// تا instanceof راهاندازی بررسی کردن // فرض کند (animal) را یک جانور 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.instanceof
را ندارند. در این صورت، منطق استاندارد استفاده میشود:obj instanceOf Class
بررسی میکند که آیاClass.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); // true // rabbit.__proto__ === Animal.prototype (مساوی نیست) // rabbit.__proto__.__proto__ === Animal.prototype (!مساوی است)
این هم تصویر چیزی که rabbit instanceof Animal
با Animal.prototype
مقایسه میکند:
راستی، همچنین متدی به نام objA.isPrototypeOf(objB) وجود دارد که اگر objA
جایی در زنجیره پروتوتایپ objB
وجود داشته باشد true
را برمیگرداند. پس بررسی obj instanceof Class
میتواند به صورت Class.prototype.isPrototypeOf(obj)
بازنویسی شود.
جالب است که سازنده Class
خودش در بررسی شرکت نمیکند! فقط زنجیره پروتوتایپها و Class.prototype
مهم هستند.
زمانی که ویژگی prototype
بعد از اینکه شیء ساخته شد تغییر کند، این موضوع میتواند باعث ایجاد پیامدهای جالبی شود.
مثل اینجا:
function Rabbit() {}
let rabbit = new Rabbit();
// پروتوتایپ را تغییر دادیم
Rabbit.prototype = {};
// !نیست (rabbit) دیگر یک خرگوش
alert( rabbit instanceof Rabbit ); // false
راهنمایی: متد Object.prototype.toString برای نوع
ما از قبل میدانیم که شیءهای ساده به صورت [object Object]
به رشته تبدیل میشوند:
let obj = {};
alert(obj); // [object Object]
alert(obj.toString()); // یکسان
این پیادهسازی toString
آنها است. اما در واقع یک ویژگی پنهانی وجود دارد که toString
را از آن خیلی قدرتمندتر میکند. میتوانیم از این متد به عنوان یک typeof
پیشرفتهتر و یک جایگزین برای instanceof
استفاده کنیم.
عجیب به نظر میرسد؟ واقعا هم هست. بیایید آن را سادهتر بیان کنیم.
با توجه به مشخصات زبان، toString
درونساخت میتواند از شیء استخراج شود و در زمینه (context) هر مقدار دیگری اجرا شود. و نتیجهاش به آن مقدار بستگی دارد.
- برای یک عدد،
[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 همانطور که در فصل دکوراتورها و ارسال کردن، متدهای call/apply توضیح داده شد برای اجرای تابع 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 ...]
است.
در نهایت ما «انواعی از استروئیدها» را داریم که نه تنها برای انواع داده اصلی کار میکند، بلکه برای شیءهای درونساخت هم کار میکند و حتی میتواند شخصیسازی شود.
زمانی که میخواهیم نوع داده را به عنوان یک رشته دریافت کنیم تا اینکه فقط بررسی کنیم، میتوانیم به جای instanceof
از {}.toString.call
برای شیءهای درونساخت استفاده کنیم.
خلاصه
بیایید متدهای بررسی نوع داده که میشناسیم را خلاصه کنیم:
کار میکند برای | برمیگرداند | |
---|---|---|
typeof |
مقدارهای اصلی | رشته |
{}.toString |
مقدارهای اصلی، شیءهای درونساخت، شیءها شامل Symbol.toStringTag |
رشته |
instanceof |
شیءها | true/false |
همانطور که میبینید، {}.toString
از لحاظ فنی یک typeof
«پیشرفتهتر» است.
زمانی که با سلسلهای از کلاسها کار میکنیم و میخواهیم بررسی کنیم که کلاس در ارثبری وجود دارد یا نه، عملگر instanceof
واقعا میدرخشد.