درک بهتر this و reference توابع در جاوااسکریپت

 

دانلود ویدیو

 

در این مطلب this و reference توابع در جاوااسکریپت را بررسی خواهیم کرد. این دو مفهوم یکی از کاربردی‌ترین مفاهیم زبان برنامه نویسی جاوااسکریپت هستند. برای درک بهتر this و reference توابع تا انتهای مطلب همراه ما باشید.

آیا تا به حال، کد جاوااسکریپت شبیه به کد زیر را مشاهده کرده‌اید؟

 

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
button.addEventListener('click', this.addItem.bind(this))
button.addEventListener('click', this.addItem.bind(this))
button.addEventListener('click', this.addItem.bind(this))

 

در این کد، bind چیست؟ و چرا پرانتز‌های تابع وجود ندارند؟ 

بهتر است سوالمان را این‌گونه بپرسیم، چرا کد بالا را به شکل کد زیر نمی‌نویسیم؟

 

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
button.addEventListener('click', this.addItem())
button.addEventListener('click', this.addItem())
button.addEventListener('click', this.addItem())

 

پاسخ کاملاً ساده است. زیرا این کد جایگزین مناسبی برای کد قبلی نیست. اما سوال اصلی اینجاست که چرا جایگزین مناسبی نیست؟

فراخوانی توابع و استفاده از reference تابع

بدیهی است که شما عملکردی مانند زیر را در جاوااسکریپت فراخوانی کرده باشید:

 

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
function someFunction() {
// do something here ...
}
someFunction()
function someFunction() { // do something here ... } someFunction()
function someFunction() {
  // do something here ...
}
someFunction()

 

و در داخل کلاس یا شئ، یک متد را به روش مشابهی فراخوانی کنید:

 

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
class MyClass {
constructor() {
this.myMethod()
}
myMethod() {
// do something here
}
}
class MyClass { constructor() { this.myMethod() } myMethod() { // do something here } }
class MyClass {
  constructor() {
    this.myMethod()
  }

  myMethod() {
    // do something here
  }
}

 

زمانی ‌که کد بالا برای اولین بار اجرا می شود، با استفاده از آن متدها نیز بلافاصله اجرا می‌شوند. در این کلاس، به محض فراخوانی سازنده، متد myMethod نیز اجرا می‌شود.

 

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
new MyClass()
new MyClass()
new MyClass()

 

با این حال، گاهی اوقات شما نمی‌خواهید که یک تابع یا متد به سرعت اجرا شود. 

یک event listener را بر روی یک دکمه (button) در نظر بگیرید:

 

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
function someFunction() { ... }
const button = document.querySelector('button');
button.addEventListener('click', someFunction());
function someFunction() { ... } const button = document.querySelector('button'); button.addEventListener('click', someFunction());
function someFunction() { ... }
const button = document.querySelector('button');

button.addEventListener('click', someFunction());

 

در این قطعه کد، بعضی از توابع برای اجرا شدن، منتظر کلیک کردن نیستند. 

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

شما می‌توانید به جای این‌که فوراً و به صورت دستی کدها را اجرا کنید، آدرس چیزهای مورد نظرتان را به جاوااسکریپت یا مرورگر بدهید. این کار از طریق ارایه مرجع (reference) صورت می‌گیرد. در شبه کد زیر یک مرجع به تابع قابل اجرا در داخل event listener داده شده است.

 

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
function someFunction() { ... };
const button = ...;
button.addEventListener('click', someFunction);
function someFunction() { ... }; const button = ...; button.addEventListener('click', someFunction);
function someFunction() { ... };

const button = ...;

button.addEventListener('click', someFunction);

 

نکته‌ای که در کد بالا به چشم می‌خورد، حذف پرانتزها پس از تابع someFunction است. حذف پرانتز به این معناست که ما دیگر تابع را فراخوانی نکرده و به جای آن یک اشاره‌گر را به تابع تخصیص می‌دهیم. این اشاره‌گر در نقش reference برای event listener ظاهر شده و در قالب یک دکمه ظاهر می‌شود.

در کلاس‌های جاوااسکریپت نیز کدی بسیار مشابه با کد بالا مورد استفاده قرار می‌گیرد. برای مثال به نمونه زیر توجه کنید:

 

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
class MyClass {
constructor() {
const button = ...;
button.addEventListener('click', this.myMethod);
}
myMethod() { ... }
class MyClass { constructor() { const button = ...; button.addEventListener('click', this.myMethod); } myMethod() { ... }
class MyClass {
  constructor() {
    const button = ...;
    button.addEventListener('click', this.myMethod);
  }

myMethod() { ... }

 

کلمه کلیدی this در کد فوق نقش مهمی را ایفا می‌کند. این کلمه اساساً به یک شیء اشاره دارد که توسط کلاس ایجاد شده است. از طرف دیگر از آنجایی که myMethod یک متد از کلاس یا شیء به شمار می‌رود، تنها از طریق this در دسترس خواهد بود.

به این ترتیب با به کارگیری چنین کدی، به جای فراخوانی بلافاصله یک متد، می‌توانید یک اشاره‌گر بر روی آن تنظیم کنید. دقیقاً به همین دلیل است که در این موارد از گذاشتن پرانتز در مقابل تابع اجتناب می‌کنیم.

 

[button class=”github-btn” href=”http://frontcast.ir/oop-in-javascript”]ویدیوی آموزشی: برنامه نویسی شی‌گرا در جاوااسکریپت[/button]

 

وقتی this رفتار عجیبی از خود نشان می‌دهد

همان‌طور که می‌دانید، از this برای دسترسی به متدها و خصوصیات یک کلاس از هر قسمتی از داخل یک کلاس یا شیء استفاده می‌شود. اما گاهی این کلیدواژه رفتار عجیبی نشان می‌دهد. به مثال زیر توجه کنید:

 

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
class CreateProduct {
constructor() {
const btn = document.querySelector('button')
this.products = ['Book 1', 'Book 2', 'Book 3']
this.count = 0
btn.addEventListener('click', this.addTitle)
}
addTitle() {
const title = new Product(this.products[this.count])
this.count++
if (this.count >= this.products.length) {
this.count = 0
}
}
}
class CreateProduct { constructor() { const btn = document.querySelector('button') this.products = ['Book 1', 'Book 2', 'Book 3'] this.count = 0 btn.addEventListener('click', this.addTitle) } addTitle() { const title = new Product(this.products[this.count]) this.count++ if (this.count >= this.products.length) { this.count = 0 } } }
class CreateProduct {
  constructor() {
    const btn = document.querySelector('button')
    this.products = ['Book 1', 'Book 2', 'Book 3']
    this.count = 0
    btn.addEventListener('click', this.addTitle)
  }

  addTitle() {
    const title = new Product(this.products[this.count])
    this.count++
    if (this.count >= this.products.length) {
      this.count = 0
    }
  }
}

 

اگر متوجه نقش و عملکرد Product نشده‌اید، به هیچ وجه نگران نباشید. در حقیقت نقش این مورد رندر کردن یک <li> جدید به همراه یک متن در داخل DOM است. با این حال اگر نگرانی شما در مورد موفقیت یا عدم موفقیت این شبه کد باشد، باید بگوییم که کاملاً حق دارید! زیرا احتمال قوی با یک خطا مواجه خواهید شد.

دلیل به وجود آمدن این خطا در این قسمت است:

 

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const title = new Product(this.products[this.count])
const title = new Product(this.products[this.count])
const title = new Product(this.products[this.count])

 

همان‌طور که مشخص است، در این شبه کد دسترسی به this.products و this.count با خطا مواجه شده است. اما دلیل این اتفاق چیست؟ آیا این خطا به اشاره‌گر کلاس یا شیء مربوط می‌شود؟

جواب این سوال منفی است. در واقع this به گونه‌ای تعریف نشده که تنها به شیء مورد نظر شما اشاره کرده و در این چهارچوب مشخص محصور بماند. در واقع this اشاره به «هر چیزی که کد را برای استفاده خاصی فراخوانی می‌کند» اشاره دارد.

در مثال بالا، دکمه مسئولیت اجرای addTitle را دارد. در نتیجه اگر ما به جای addTitle مقدار this را بخواهیم، می‌توانیم به این شیوه عمل کنیم:

 

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
addTitle() {
console.log(this);
...
}
addTitle() { console.log(this); ... }
addTitle() {
  console.log(this);
  ...
}

 

بنابراین در این حالت this اشاره به همان تگ <button> دارد که ما آن را در event listener اضافه کرده‌ایم. این دقیقاً رفتار پیش‌فرض جاوااسکریپت را به ما نمایش می‌دهد. به عبارت دیگر this اشاره به چیزی دارد که متد دارای this را فراخوانی کرده است.

مشخصاً ما خواهان چنین رفتاری نیستیم. اما خوشبختانه به سادگی می‌توانیم با ایجاد تغییراتی کوچک، به هدف خود دست یابیم. شما می‌توانید به جای addTitle، کلیدواژه this را به چیز دیگری به غیر از دکمه متصل کنید. برای مثال همانند کد زیر می‌توانید this را به یک شیء یا کلاس وصل کنید:

 

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
btn.addEventListener('click', this.addTitle.bind(this))
btn.addEventListener('click', this.addTitle.bind(this))
btn.addEventListener('click', this.addTitle.bind(this))

 

 

bind یک متد پیش‌فرض در جاوااسکریپت است که می‌توان آن را در توابع و یا متدهای مورد نظر فراخوانی کرد. با استفاده از این متد می‌توانید به جای اتصال this به متد یا تابع، آن را به هر مقدار منتخبی متصل کنید.

در شبه کد فوق، ما به جای اتصال this به addTitle، آن را به مقدار مشابهی متصل کرده‌ایم که this در تابع سازنده به آن اشاره دارد. در این سازنده، this اشاره به کلاس یا شیء دارد. چرا که خود ما اقدام به اجرای این کد کرده‌ایم. در واقع سازنده توسط شی تعریف شده توسط شما اجرا می‌شود. از همین رو کلیدواژه this به جای اشاره به سازنده، به همان شیء اجرا کننده آن اشاره می‌کند.

 

[button class=”github-btn” href=”http://frontcast.ir/course/javascript-advanced”]دوره جامع و پیشرفته جاوااسکریپت[/button]

دیدگاه‌ها:

mrhajian

آبان 4, 1399  در  2:43 ق.ظ

عذر میخام در دستور
( this.addTitle.bind ( this
bind دقیقا چیکار میکنه؟
معنی لغوی bind بستن هستش فکر میکنم میاد و this را از چیزی که در حال حاضر بهش اشاره میشه را میبنده تا درواقع به this یک اسکوپ بالاتر اشاره کنه درسته؟

aminda

اسفند 11, 1398  در  10:39 ق.ظ

سلام
خیلی ممنون جناب صدری
پست خیلی خوبی و کاربردی بود…
با اینکه این توضیحات(. بقیه مطالب شما و بعضی از رفرنس های دیگه) رو خوب متوجه میشم من ولی هرچی در مورد this و bind میخونم باز هم حس میکنم کمه….
انگار هنوز قشنگ درکشون نکردم !

مسعود صدری

اسفند 13, 1398  در  4:44 ب.ظ

سلام
ممنون از شما.
این یکی از مفاهیم نسبتا پیچیده توی جاوااسکریپت هست.
پیشنهاد می‌کنم تمرینی که انجام دادم رو شما هم یک بار انجام بدین.
بعد از اون یک تمرین جدا رو برای خودتون در نظر بگیرید و با استفاده از مفاهیمی که یاد گرفتین سعی کنید انجام بدین.

افزودن دیدگاه جدید