مطمئنا با توابع Arrow در جاوااسکریت آشنا هستید و احتمالا همیشه از آن‌ها استفاده می‌کنید. این تابع‌ در بروزرسانی ECMAScript6 در سال ۲۰۱۵ معرفی شد و به سرعت به محبوبیت زیادی دست یافت. می‌توان گفت دلیل خوبی برای این محبوبیت وجود داشت: تابع Arrow دارای ساختاری است که باعث سهولت در برنامه‌نویسی (syntactic sugar) می‌شود و منجر به حذف نیازمندی‌های زیر می‌گردد:

توابع Arrow در جاوااسکریت علاوه‌بر موارد فوق، برخی از پیچیدگی‌های موجود در scope یا دامنه توابع جاوااسکریپت و تعداد استفاده از کلمه کلیدی this را کاهش می‌دهد. زیرا که گاهی‌ اوقات در برنامه‌نویسی نیازمند استفاده از تابع بدون‌ نام (anonymous function) هستیم.

با این‌همه، توابع Arrow راه‌ حل واحدی برای تمام نیازمندی‌ها هنگام نوشتن توابع در جاوااسکریپت نیست. در ادامه به موقعیت‌هایی که استفاده از تابع Arrow راه‌ حل مناسب و صحیحی نیست، می‌پردازیم.

Object Methods

فرض کنید بخواهیم متدی برای اتصال (bind) به یک object ایجاد کنیم:

 

const mario = {
    lives: 3,
    oneUp: () => {
        this.lives++;
    }
}

 

در این مثال، اگر mario.oneUp() را فراخوانی کنیم، انتظار داریم مقدار متغیر mario.lives از ۳ به ۴ افزایش یابد. با‌ این‌حال، همان‌طور که نوشته‌شده‌است، مقدار lives بدون توجه به این‌که چندبار متد oneUp() فراخوانی شود، هیچ تغییری نخواهد‌کرد.

علت این اتفاق استفاده از کلمه‌کلیدی this است.

چنانچه در MDN نوشته شده است:

“یک تابع Arrow کلمه‌کلیدی this اختصاصی خود را ندارد و مقدار this موجود در enclosing lexical scope را استفاده می‌کند. lexical scope در جایی که متغیر تعریف شده‌است، استفاده می‌شود و مشخص می‌کند متغیر در کدام قسمت های کد در دسترس است. توابع Arrow از قوانین جستجوی متغیر عادی پیروی می‌کنند. بنابراین در حین جستجو برای یافتن مقدار this، از آنجائیکه در scope فعلی وجود ندارد، تابع Arrow نهایتا مقدار this را از enclosing scope خود پیدا می‌کند.”

enclosing scope در مورد کد فوق، به window object محدود است. فراخوانی oneUp() از برنامه‌ می‌خواهد که مقدار lives را در window object افزایش دهد. از آنجائیکه این متغیر در scope آن وجود ندارد، کد ما کار نخواهد‌کرد.

 

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

 

در‌عوض، از سینتکس متد اول و سنتی تابع استفاده می‌کنیم، که متغیر this تابع را در هنگام فراخوانی تابع به object مشخصی متصل (Bind) می‌کند.

 

const mario = {
  lives: 3,
  oneUp: function() {
    this.lives++;
  }
};

 

Object Prototype

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

 

class Robot {
  constructor(name, catchPhrase) {
    this.name = name;
    this.catchPhrase = catchPhrase;
  }
};

Robot.prototype.speak = () => {
  console.log(this === window);
  return this.catchPhrase
};

const ironG = new Robot("Iron Giant", "Be good");

ironG.speak();

 

فراخوانی تابع نوشته‌شده در خط ۱۵، خروجی زیر را به ما خواهد داد:

 

true
undefined

 

در کد فوق تابع speak() را تعریف کرده و به عنوان catchphrase در Robot object قرار داده‌ایم، پس چه علتی باعث می‌شود که این کد به عنوان تعریف‌ نشده (undefined) ارزیابی شود؟

دلیل این اتفاق در console.log() مشخص است. همان‌طور که مشاهده می‌کنید زمانی‌که از console بخواهیم تا ( this === window) را ارزیابی کند، مقدار true برمی‌گرداند. این مورد اثباتی برای مثال قبلی متدهای object که بحث کردیم، فراهم می‌کند. 

برای کار با توابعی که نیازمند context یا متن هستند، لازم است از سینتکس تابع متداول (و نه Arrow Function) استفاده کنیم تا برای اتصال درست this، دچار مشکل نشویم.

 

Robot.prototype.speak = function() {
  console.log(this === ironG); // true
  return this.catchphrase;
};

 

Dynamic Context

مثال نهایی:

 

const button = document.querySelector(#darkMode);
button.addEventListener('click', () => {
  this.classList.toggle('on');
});

 

اکنون به احتمال زیاد متوجه شده‌اید که نه تنها کد فوق کار نمی‌کند، بلکه دلیل آن را هم می‌دانید. نکته مهم در این مثال نیز، با this ارتباط دارد.

سینتکس تابع Arrow، متن (context) را به صورت استاتیک (Static) به محض تعریف تابع به آن متصل می‌کند. این عملکرد در تضاد با هدفی است که می‌خواهیم هنگام کار‌کردن با event handler ها و event listener ها، که ذاتا پویا(Dynamic) هستند، به آن دست ‌یابیم. 

زمانی‌ که در DOM با استفاده از event handler ها و event listener ها تغییراتی ایجاد می‌کنیم، رویدادهایی که trigger می‌شوند به this متعلق به المان مدنظر اشاره خواهند‌ کرد.

برای یک تابع Arrow که در متن اجرای global تعریف شده‌ باشد، this به window اشاره خواهد‌ داشت. لذا در کد فوق، this.classList به عنوان window.classList ارزیابی شده و نتیجه TypeError خواهد بود.

 

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