زمانی که شیءها بهم اضافه شوند obj1 + obj2
، از هم کم شوند obj1 - obj2
یا با استفاده از alert(obj)
چاپ شوند چه اتفاقی میافتد؟
جاوااسکریپت اجازه نمیدهد که چگونگی کار کردن عملگرها روی شیءها را شخصیسازی کنیم. بر خلاف بعضی از زبانهای برنامهنویسی، مثل Ruby یا C++، ما نمیتوانیم یک متد خاص شیء پیادهسازی کنیم تا اضافه کردن (یا بقیه عملگرها) را کنترل کند.
در صورت وجود چنین عملیاتی، شیءها به طور خودکار به مقدار اصلی تبدیل و سپس عملیات با این مقدارهای اصلی انجام میشوند و نتایج به شکل یک مقدار اصلی است.
این محدودیت مهمی است چون نتیجه obj1 + obj2
(یا عملیات ریاضی دیگری) نمیتواند شیء دیگری باشد!
برای مثال ما نمیتوانیم کاری کنیم که شیءها بردارها یا ماتریسها (یا دستاوردها یا هرچیزی) را نمایش دهند، آنها را بهم اضافه کنند و توقع یک شیء «جمعشده» را به عنوان نتیجه داشته باشیم. چنین شاهکارهای معماری به طور خودکار «خارج از بحث» هستند.
پس چون نمیتوانیم در این باره از لحاظ فنی کار خاصی کنیم، در پروژههای واقعی شیءها همراه با ریاضیات استفاده نمیشوند. زمانی که اتفاق میافتد، معمولا بخاطر یک اشتباه کدنویسی است.
در این فصل ما چگونگی تبدیل یک شیء به مقدار اصلی و شخصیسازی آن را پوشش میدهیم.
ما دو هدف داریم:
- این کار به ما اجازه میدهد که متوجه شویم در صورت بروز اشتباههای کدنویسی چه چیزی در حال رخ دادن است، زمانی که چنین عملیاتی به صورت تصادفی اتفاق افتاد.
- استثناهایی وجود دارد که چنین عملیاتی مجاز هستند و خوب بنظر میرسند. مثلا تفریق یا مقایسه تاریخها (شیءهای
Date
). ما بعدا به سراغ آن میرویم.
قوانین تبدیل
در فصل تبدیل نوع داده ما قوانینی برای تبدیل عددی، رشتهای و بولین مقدارهای اصلی را دیدیم. اما ابهامی برای شیءها به جا گذاشتیم. حالا، چون درباره متدها و سمبلها میدانیم این موضوع برای پوشش دادن آماده است.
- تبدیل به بولین وجود ندارد. در زمینه بولین تمام شیءها
true
هستند. فقط تبدیل عددی و رشتهای وجود دارد. - تبدیل عددی زمانی که ما شیءها را از هم کم میکنیم یا تابعهای ریاضی را اعمال میکنیم اتفاق میافتد. برای مثال، شیءهای
Date
(در فصل تاریخ و زمان پوشش داده میشوند) میتوانند از هم کم شوند و نتیجهdate1 - date2
برابر با تفاوت زمانی بین دو تاریخ است. - همینطور برای تبدیل رشتهای – این تبدیل زمانی که ما یک شیء را خروجی میگیریم مثل
alert(obj)
و در زمینههای مشابه اتفاق میافتد.
میتوانیم با استفاده از متدهای خاص شیء تبدیل رشتهای و عددی را پیادهسازی کنیم.
حال بیایید به جزئیات فنی وارد شویم چون تنها راه پوشش دادن این موضوع همین است.
جزءها (Hints)
جاوااسکریپت چگونه تشخیص میدهد کدام تبدیل را اعمال کند؟
سه نوع تبدیل داده وجود دارد که در موقعیتهای گوناگون اتفاق میافتد. همانطور که در مشخصات زبان گفته شده، به آنها «جزء (hint)» میگویند:
"string"
-
برای تبدیل شیء به رشته، زمانی که ما در حال انجام کاری روی شیءای هستیم که توقع یک رشته دارد، مثل
alert
:// خروجی alert(obj); // استفاده از شیء به عنوان کلید ویژگی anotherObj[obj] = 123;
"number"
-
برای تبدیل شیء به عدد، مثل زمانی که ما از ریاضی استفاده میکنیم:
// تبدیل واضح let num = Number(obj); // ریاضیات (به غیر از عملگر مثبت دوگانه) let n = +obj; // مثبت دوگانه let delta = date1 - date2; // مقایسه بزرگتر/کمتر let greater = user1 > user2;
اکثر تابعهای ریاضیاتی درونساخت هم چنین تبدیلی را شامل میشوند.
"default"
-
در موارد کمیاب زمانی که عملگر «مطمئن نیست» که چه نوعی را دریافت میکند.
برای مثال، عملگر مثبت دوگانه
+
هم میتواند با رشتهها کار کند (آنها را ادغام کند) و هم با عددها (آنها را اضافه میکند). پس اگر یک مثبت دوگانه شیءای را دریافت کند، از جزء"default"
برای تبدیل آن استفاده میکند.همچنین، اگر شیءای با استفاده از
==
با یک رشته، عدد یا سمبل مقایسه شود، معلوم نیست که کدام تبدیل باید انجام شود پس جزء"default"
استفاده میشود.// استفاده میکند "default" مثبت دوگانه از جزء let total = obj1 + obj2; // استفاده میکند "default" از جزء obj == number if (user == 1) { ... };
عملگرهای مقایسه کمتر و بزرگتر، مانند
<
>
، میتوانند هم با رشتهها و هم با عددها کار کنند. با این حال، این عملگرها از جزء"number"
استفاده میکنند نه"default"
بنا به دلایلی مربوط به گذشته.
اگرچه در عمل، قضایا کمی سادهتر هستند.
تمام شیءهای درونساخت به جز یک مورد (شیء Date
، بعدا آن را یاد خواهیم گرفت) تبدیل "default"
را مانند "number"
پیادهسازی میکنند. و احتمالا ما هم باید همین کار را کنیم.
با این حال، دانشتن درباره تمام 3 جزء مهم است. به زودی دلیل آن را خواهیم دید.
برای انجام تبدیلها، جاوااسکریپت سعی میکند که سه متد شیء را پیدا و فراخوانی کند:
- فراخوانی
obj[Symbol.toPrimitive](hint)
– متدی شامل کلید سمبلیSymbol.toPrimitive
(سمبلِ سیستم)، اگر چنین متدی وجود داشته باشد، - در غیر این صورت اگر جزء
"string"
باشد- متد
obj.toString()
وobj.valueOf()
را امتحان کن، هر کدام که وجود داشته باشد.
- متد
- در غیر این صورت اگر جزء
"number"
یا"default"
باشد- متد
obj.valueOf()
andobj.toString()
را امتحان کن، هر کدام که وجود داشته باشد.
- متد
متد Symbol.toPrimitive
بیایید از اولین متد شروع کنیم. یک سمبل درونساخت به نام Symbol.toPrimitive
وجود دارد که باید برای نامگذاری متد تبدیل استفاده شود، مثلا اینگونه:
obj[Symbol.toPrimitive] = function(hint) {
// اینجا کدی برای تبدیل این شیء به مقدار اصلی قرار میگیرد
// این کد باید یک مقدار اصلی برگرداند
// "string" ،"number" ،"default" یکی از = hint
};
اگر متد Symbol.toPrimitive
وجود داشته باشد، برای تمام جزءها استفاده میشود و متد دیگری نیاز نیست.
برای مثال، اینجا شیء user
این متد را پیادهسازی میکند:
let user = {
name: "John",
money: 1000,
[Symbol.toPrimitive](hint) {
alert(`hint: ${hint}`);
return hint == "string" ? `{name: "${this.name}"}` : this.money;
}
};
// دموی تبدیل کردن:
alert(user); // hint: string -> {name: "John"}
alert(+user); // hint: number -> 1000
alert(user + 500); // hint: default -> 1500
همانطور که از کد میبینیم، با توجه به تبدیل، user
به رشتهای خودتعریفکننده یا مقداری پول تبدیل میشود. متد user[Symbol.toPrimitive]
به تنهایی تمام موارد تبدیل را به عهده میگیرد.
متد toString/valueOf
اگر Symbol.toPrimitive
وجود نداشته باشد، سپس جاوااسکریپت سعی میکند که متدهای toString
و valueOf
را پیدا کند:
- برای جزء “string”:
toString
و اگر این متد وجود نداشت یا به جای یک مقدار اصلی شیءای را برگرداند، سپسvalueOf
(پسtoString
برای تبدیلهای رشتهای اولویت دارد). - برای بقیه جزءها:
valueOf
و اگر این متد وجود نداشت یا به جای یک مقدار اصلی شیءای را برگرداند، سپسtoString
(پسvalueOf
برای ریاضیات اولویت دارد).
متدهای toString
و valueOf
از زمانهای گذشته وجود دارند. آنها سمبل نیستند (سمبلها انقدر قدیمی نیستند) بلکه متدهای «معمولی» هستند که اسمی رشتهای دارد. آنها راهی جایگزین برای پیادهسازی تبدیل به «سبک قدیمی» را فراهم میکنند.
این متدها باید یک مقدار اصلی برگردانند. اگر toString
یا valueOf
یک شیء برگرداند، سپس این مقدار نادیده گرفته میشود (مثل این است که متدی وجود نداشته باشد).
به صورت پیشفرض، یک شیء ساده متدهای toString
و valueOf
پایین را دارد:
- متد
toString
رشته"[object Object]"
را برمیگرداند. - متد
valueOf
خود شیء را برمیگرداند.
اینجا یک دمو داریم:
let user = {name: "John"};
alert(user); // [object Object]
alert(user.valueOf() === user); // true
پس اگر ما سعی کنیم که از شیء به عنوان یک رشته استفاده کنیم، مثلا درون alert
یا بقیه، سپس به صورت پیشفرض ما [object Object]
را میبینیم.
متد valueOf
پیشفرض فقط برای کامل بودن مطالب اینجا گفته شد تا از هر سردرگمی جلوگیری شود. همانطور که میبینید، خود شیء را برمیگرداند پس نادیده گرفته میشود. نپرسید چرا، دلیلش مربوط به گذشته است. پس ما میتوانیم فرض کنیم که این متد وجود ندارد.
بیایید این متدها را برای شخصیسازی تبدیل پیادهسازی کنیم.
برای مثال، اینجا user
با استفاده از ترکیب toString
و valueOf
به جای Symbol.toPrimitive
کار یکسانی با کد بالا را انجام میدهد:
let user = {
name: "John",
money: 1000,
// hint="string" به ازای
toString() {
return `{name: "${this.name}"}`;
},
// "default" یا hint="number" به ازای
valueOf() {
return this.money;
}
};
alert(user); // toString -> {name: "John"}
alert(+user); // valueOf -> 1000
alert(user + 500); // valueOf -> 1500
همانطور که میبینیم، رفتار این کد با مثال قبل که شامل Symbol.toPrimitive
بود یکسان است.
اغلب اوقات ما یک چیز «همهگیر» میخواهیم که تمام تبدیلهای مقدار اصلی را کنترل کند. در این مورد، میتوانیم فقط toString
را پیادهسازی کنیم، مثلا اینگونه:
let user = {
name: "John",
toString() {
return this.name;
}
};
alert(user); // toString -> John
alert(user + 500); // toString -> John500
در نبود Symbol.toPrimitive
و valueOf
، متد toString
تمام تبدیلهای مقدار اصلی را به عهده میگیرد.
یک تبدیل میتواند هر نوع مقدار اصلی را برگرداند
چیز مهمی که باید درباره تمام متدهای تبدیل به مقدار اصلی بدانیم این است که آنها حتما مقدار اصلی «جزئی» را برنمیگردانند.
هیچ کنترلی بر روی اینکه toString
دقیقا رشته برگرداند یا اینکه متد Symbol.toPrimitive
برای جزء "number"
حتما یک عدد برگرداند نیست.
تنها مورد الزامی: این متدها باید یک مقدار اصلی برگردانند نه یک شیء.
بنا به دلایلی مربوط به گذشته، اگر toString
یا valueOf
یک شیء برگرداند، اروری ایجاد نمیشود، اما چنین مقداری نادیده گرفته میشود (انگار که متد وجود ندارد). به این دلیل که در گذشته مفهوم «ارور» مناسبی در جاوااسکریپت وجود نداشت.
در مقابل، Symbol.toPrimitice
باید مقدار اصلی برگرداند، در غیر این صورت یک ارور خواهیم داشت.
تبدیلهای بیشتر
همانطور که از قبل میدانیم، بسیاری از عملگرها و تابعها تبدیل نوع داده را انجام میدهند، مثلا عملگر ضرب *
عملوندها را به عدد تبدیل میکند.
اگر ما شیء را به عنوان یک آرگومان پاس دهیم سپس دو مرحله از محاسبات وجود خواهد داشت:
- شیء به یک مقدار اصلی تبدیل میشود (با استفاده از قوانینی که بالاتر گفتیم).
- اگر برای محاسبات بعدی لازم باشد، مقدار اصلی حاصل باز هم تبدیل میشود.
برای مثال:
let obj = {
// در نبود بقیه متدها تمام تبدیلها را به عهده میگیرد toString
toString() {
return "2";
}
};
alert(obj * 2); // شیء به مقدار اصلی "2" تبدیل شد، عملگر ضرب آن را به عدد تبدیل کرد، 4
- ضرب
obj * 2
ابتدا شیء را به مقدار اصلی تبدیل میکند (برابر است با رشته"2"
). - سپس
"2" * 2
تبدیل میشود به2 * 2
(رشته به عدد تبدیل میشود).
عملگر مثبت دوگانه همانطور که با خوشحالی یک رشته را قبول میکند، رشتهها را در موقعیت یکسان ادغام میکند:
let obj = {
toString() {
return "2";
}
};
alert(obj + 2); // تبدیل به مقدار اصلی یک رشته برگرداند => ادغام، (2 + "2") 22
خلاصه
تبدیل شیء به مقدار اصلی توسط بسیاری از تابعها و عملگرهای درونساخت که مقداری اصلی را به عنوان ورودی قبول میکنند به طور خودکار فراخوانی میشود.
سه نوع (جزء) از آن وجود دارد:
"string"
(برایalert
و عملیات دیگر که به رشته نیاز دارند)"number"
(برای ریاضیات)"default"
(برای عملگرها، معمولا شیءها آن را مانند"number"
پیادهسازی میکنند)
مشخصات زبان به طور واضح بیان کرده است که کدام عملگر از کدام جزء استفاده میکند.
الگوریتم تبدیل:
- فراخوانی
obj[Symbol.toPrimitive](hint)
در صورتی که وجود داشت، - در غیر این صورت اگر جزء
"string"
بود- متدهای
obj.toString()
وobj.valueOf()
را امتحان کن، هر کدام که وجود داشت.
- متدهای
- در غیر این صورت اگر جزء
"number"
یا"default"
بود- متدهای
obj.valueOf()
وobj.toString()
را امتحان کن، هر کدام که وجود داشت.
- متدهای
تمام این متدها باید یک مقدار اصلی را برگردانند تا کار انجام شود (اگر تعریف شده باشد).
در عمل، اینکه فقط obj.toString()
را به عنوان متدی «همهگیر» برای تبدیلهای رشتهای که باید یک نمایش «قابل خواندن برای انسان» از شیء را برگدانند، برای اهداف دیباگ یا لاگ گرفتن، اغلب اوقات کافی است.