۲۴ اکتبر ۲۰۲۱

پیوند تابع

زمانی که متدهای تابع را به عنوان callback پاس می‌دهیم، برای مثال به setTimeout، یک مشکل شناخته شده وجود دارد: «از دست دادن this».

در این فصل ما راه‌هایی را برای رفع آن خواهیم دید.

از دست دادن “this”

ما از قبل درباره از دست دادن this مثال‌هایی را دیده‌ایم. زمانی که یک متد جایی به غیر از شیء خودش پاس داده شود، this از دست می‌رود.

چیزی که ممکن است با setTimeout اتفاق بیافتد اینجا آورده شده:

let user = {
  firstName: "John",
  sayHi() {
    alert(`سلام، ${this.firstName}!`);
  }
};

setTimeout(user.sayHi, 1000); // !undefined ،سلام

همانطور که می‌بینیم، خروجی “John” را به عنوان this.firstName نشان نداد بلکه undefined را نمایش داد!

دلیلش این است که setTimeout تابع user.sayHi را جدای از شیء آن دریافت کرد. خط آخر می‌تواند اینگونه نوشته شود:

let f = user.sayHi;
setTimeout(f, 1000); // را از دست داد user زمینه

روش setTimeout در مرورگر کمی خاص است: این تابع برای فراخوانی تابع this=window را تنظیم می‌کند (در Node.js، مقدار this شیء تایمر می‌شود اما اینجا خیلی مهم نیست). پس برای this.firstName این تابع تلاش می‌کند که window.firstName را دریافت کند، که وجود ندارد. در موارد مشابه دیگر، معمولا this برابر با undefined می‌شود.

کاری که انجام می‌شود کاملا معمولی است، ما می‌خواهیم یک متد شیء را جایی دیگر (اینجا، به زمان‌بند) که فراخوانی خواهد شد پاس دهیم. چگونه مطمئن شویم که با زمینه درست فراخوانی می‌شود؟

راه‌حل 1: دربرگیرنده

ساده‌ترین راه‌حل استفاده از یک تابع دربرگیرنده است:

let user = {
  firstName: "John",
  sayHi() {
    alert(`سلام، ${this.firstName}!`);
  }
};

setTimeout(function() {
  user.sayHi(); // !John ،سلام
}, 1000);

حالا کار می‌کند، چون user را از محیط لغوی بیرونی دریافت می‌کند و سپس به طور معمولی متد را فراخوانی می‌کند.

این یکسان اما کوتاه‌تر است:

setTimeout(() => user.sayHi(), 1000); // !John ،سلام

مناسب بنظر می‌رسد اما یک آسیب‌پذیری جزئی ممکن است در ساختار کد ما نمایان شود.

اگر قبل از اینکه setTimeout فعال شود (تاخیر یک ثانیه‌ای وجود دارد!) user مقدارش تغییر کند چه؟ سپس ناگهان، شیء اشتباهی را فراخوانی می‌کند!

let user = {
  firstName: "John",
  sayHi() {
    alert(`سلام، ${this.firstName}!`);
  }
};

setTimeout(() => user.sayHi(), 1000);

// ...در حین 1 ثانیه تغییر می‌کند user مقدار
user = {
  sayHi() { alert("!setTimeout دیگر در user یک"); }
};

// !setTimeout دیگر در user یک

راه‌حل بعدی تضمین می‌کند که چنین چیزی اتفاق نیافتد.

راه‌حل 2: متد bind

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

سینتکس پایه‌ای آن: