Explicit Binding: کنترل مستقیم this با call، apply و bind

مقدمه

در درس قبل دیدیم که Implicit Binding می‌تواند به‌راحتی از دست برود؛ برای مثال وقتی یک متد از شیء اصلی خودش جدا شود. جاوااسکریپت برای حل دقیق همین مشکل، و همچنین برای مواردی که می‌خواهیم صراحتاً خودمان تعیین کنیم this چه مقداری داشته باشد، سه ابزار در اختیار می‌گذارد: call، apply و bind. به این قانون، Explicit Binding می‌گویند؛ چون به‌جای اینکه جاوااسکریپت خودش بر اساس نحوه‌ی فراخوانی تصمیم بگیرد، ما مستقیماً و صریحاً مقدار this را مشخص می‌کنیم.

call — فراخوانی فوری با 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 انجام می‌دهد، با یک تفاوت: به‌جای پاس دادن آرگومان‌ها یکی‌یکی، آن‌ها را در قالب یک آرایه دریافت می‌کند:

استفاده از apply با آرگومان‌های آرایه‌ای
خروجی کنسول اینجا نمایش داده می‌شود...

تحلیل رفتار جاوااسکریپت

خروجی این کد دقیقاً همان چیزی است که با call در مثال قبل دیدیم؛ تنها تفاوت، شکل پاس دادن آرگومان‌هاست. apply به‌خصوص زمانی مفید است که آرگومان‌ها از قبل در قالب یک آرایه موجود باشند (مثلاً نتیجه‌ی یک map یا filter) و نخواهیم آن‌ها را دستی از آرایه بیرون بکشیم.

جدول تفاوت call و apply

ویژگیcallapply
آرگومان اولمقدار thisمقدار this
آرگومان‌های بعدیجداگانه و یکی‌یکیدر قالب یک آرایه
زمان فراخوانیفوریفوری

bind — ساخت یک نسخه‌ی جدید از تابع با this ثابت

برخلاف call و apply که بلافاصله تابع را فراخوانی می‌کنند، متد bind تابع را فوراً اجرا نمی‌کند؛ بلکه یک تابع جدید برمی‌گرداند که مقدار this آن برای همیشه به مقدار مشخص‌شده، ثابت (Bound) شده است:

ساخت تابعی با this ثابت‌شده به وسیله‌ی bind
خروجی کنسول اینجا نمایش داده می‌شود...

تحلیل کد

اینجا دقیقاً همان سناریوی مشکل‌سازی که در درس قبل دیدیم رخ داده: متد 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 حیاتی است.