Explicit Binding: کنترل مستقیم this با call، apply و bind
مقدمه
در درس قبل دیدیم که Implicit Binding میتواند بهراحتی از دست برود؛ برای مثال وقتی یک متد از شیء اصلی خودش جدا شود. جاوااسکریپت برای حل دقیق همین مشکل، و همچنین برای مواردی که میخواهیم صراحتاً خودمان تعیین کنیم this چه مقداری داشته باشد، سه ابزار در اختیار میگذارد: call، apply و bind. به این قانون، Explicit Binding میگویند؛ چون بهجای اینکه جاوااسکریپت خودش بر اساس نحوهی فراخوانی تصمیم بگیرد، ما مستقیماً و صریحاً مقدار this را مشخص میکنیم.
call — فراخوانی فوری با this مشخص
متد call یک تابع را بلافاصله فراخوانی میکند، اما اولین آرگومان آن، مقداری است که باید بهعنوان this درون تابع استفاده شود:
تحلیل کد
تابع greet هیچ ارتباط مستقیمی با user یا admin ندارد و به هیچکدام متصل نیست. اما با استفاده از call، صریحاً به جاوااسکریپت میگوییم که در این فراخوانی خاص، this باید برابر user باشد، و در فراخوانی بعدی، برابر admin. این دقیقاً همان راهحلی است که برای مشکل «از دست رفتن Implicit Binding» که در درس قبل دیدیم، به کار میرود.
پاس دادن آرگومانهای اضافی به call
اگر خودِ تابع نیاز به پارامتر داشته باشد، آرگومانهای اضافی را میتوان بعد از مقدار this، یکییکی و جدا از هم، به call پاس داد:
function introduce(city, job) {
console.log(this.name + " در " + city + " و شغلش " + job + " است");
}
let user = { name: "علی" };
introduce.call(user, "تهران", "برنامهنویس");
// علی در تهران و شغلش برنامهنویس است
apply — مشابه call، اما با آرایهی آرگومان
متد apply دقیقاً همان کاری را انجام میدهد که call انجام میدهد، با یک تفاوت: بهجای پاس دادن آرگومانها یکییکی، آنها را در قالب یک آرایه دریافت میکند:
تحلیل رفتار جاوااسکریپت
خروجی این کد دقیقاً همان چیزی است که با call در مثال قبل دیدیم؛ تنها تفاوت، شکل پاس دادن آرگومانهاست. apply بهخصوص زمانی مفید است که آرگومانها از قبل در قالب یک آرایه موجود باشند (مثلاً نتیجهی یک map یا filter) و نخواهیم آنها را دستی از آرایه بیرون بکشیم.
جدول تفاوت call و apply
| ویژگی | call | apply |
|---|---|---|
| آرگومان اول | مقدار this | مقدار this |
| آرگومانهای بعدی | جداگانه و یکییکی | در قالب یک آرایه |
| زمان فراخوانی | فوری | فوری |
bind — ساخت یک نسخهی جدید از تابع با this ثابت
برخلاف call و apply که بلافاصله تابع را فراخوانی میکنند، متد bind تابع را فوراً اجرا نمیکند؛ بلکه یک تابع جدید برمیگرداند که مقدار this آن برای همیشه به مقدار مشخصشده، ثابت (Bound) شده است:
تحلیل کد
اینجا دقیقاً همان سناریوی مشکلسازی که در درس قبل دیدیم رخ داده: متد greet از شیء user جدا شده و در متغیر boundGreet ذخیره شده است. اما اینبار، پیش از جدا شدن، با bind(user) صراحتاً this را برای همیشه به user متصل کردهایم. به همین دلیل، برخلاف مثال مشابه در درس قبل، فراخوانی مستقل boundGreet() بدون مشکل کار میکند و this همچنان بهدرستی user است.
bind بهخصوص برای Callback ها حیاتی است
یکی از رایجترین کاربردهای bind، اطمینان از حفظ this درست هنگام پاس دادن یک متد بهعنوان Callback است؛ سناریویی که در فصل توابع پیشرفته، هنگام بررسی Callback، دیدیم که چقدر رایج است:
let user = {
name: "علی",
greet: function () {
console.log("سلام، من " + this.name + " هستم");
}
};
setTimeout(user.greet, 1000); // this از دست میرود
setTimeout(user.greet.bind(user), 1000); // this بهدرستی حفظ میشود
در خط اول، وقتی user.greet بدون bind به setTimeout پاس داده میشود، دقیقاً همان اتفاق درس قبل رخ میدهد: تابع از شیء اصلیاش جدا شده و this آن به Default Binding برمیگردد. در خط دوم، با bind(user) از قبل، این مشکل کاملاً برطرف شده است.
bind با آرگومانهای از پیش تعیینشده (Partial Application)
bind علاوه بر تعیین this، امکان تعیین از پیش برخی آرگومانهای تابع را نیز فراهم میکند:
function multiply(a, b) {
return a * b;
}
let double = multiply.bind(null, 2);
console.log(double(5)); // 10 — چون a=2 از قبل تعیین شده، فقط b=5 باقی میماند
در این مثال، چون multiply از نظر this اهمیتی ندارد، آرگومان اول bind برابر null گذاشته شده است. آرگومان دوم (2) از قبل برای پارامتر a تعیین میشود؛ به همین دلیل double تابعی است که فقط یک آرگومان باقیمانده (b) را میپذیرد. این تکنیک شباهت مفهومی زیادی به Function Factory دارد که در فصل قبل، هنگام بررسی Closure، دیدیم.
جدول جمعبندی هر سه متد
| متد | زمان فراخوانی تابع | خروجی | شکل آرگومانها |
|---|---|---|---|
| call | فوری | نتیجهی اجرای تابع | جداگانه |
| apply | فوری | نتیجهی اجرای تابع | آرایه |
| bind | به تعویق میافتد | یک تابع جدید | جداگانه |
ارتباط با سایر مفاهیم
Explicit Binding مستقیماً راهحل مشکلی است که در درس قبل، دربارهی از دست رفتن Implicit Binding دیدیم. این سه متد در فصلهای بعدی، بهویژه هنگام کار با Event Listener ها در Browser، بارها مورد استفاده قرار خواهند گرفت. در درس بعدی همین فصل، با Constructor Binding آشنا میشویم که قانون چهارم و آخرین قانون سنتی تعیین this است.
جمعبندی
call، apply و bind سه ابزار برای تعیین صریح مقدار this هستند. call تابع را بلافاصله با آرگومانهای جداگانه فراخوانی میکند؛ apply مشابه آن است اما آرگومانها را در قالب یک آرایه میپذیرد. bind، برخلاف آن دو، تابع را فوراً اجرا نمیکند، بلکه یک نسخهی جدید از تابع با this ثابتشده برمیگرداند؛ ابزاری که بهخصوص برای حفظ this درست هنگام پاس دادن متد بهعنوان Callback حیاتی است.
