Arrow Function در جاوااسکریپت با استاندارد ES2015 معرفی شده است و نسبت به توابع معمولی سینتکس سادهتری دارد، اما همانطور که در این مقاله خواهیم دید، تفاوتهای مهمی در نحوه عملکرد آن با توابع سنتی وجود دارد.
در حالی که arrow functionها تقریباً در هر جایی که یک تابع معمولی استفاده میشود، قابل استفاده هستند، چند استثنا نیز وجود دارد. این توابع سینتکسی فشرده دارند و مانند توابع معمولی، شامل لیستی از آرگومانها، بدنه تابع و مقدار بازگشتی احتمالی میباشند.
در ادامه، جزئیات Arrow Functionها را بررسی خواهیم کرد، اما بهطور کلی، زمانی که نیاز به یک binding جدید برای
this
داریم، نباید از آنها استفاده کنیم. Arrow Functionها this
مخصوص به خود ندارند و مقدار this
را از scope بیرونی خود به ارث میبرند.
همچنین، از Arrow Functionها نمیتوانیم به عنوان constructor یا تابع generator استفاده کنیم، زیرا این توابع نمیتوانند حاوی دستور
yield
باشند.
یک Arrow Function در جاوااسکریپت شامل لیستی از آرگومانها، یک فلش (
=>
) و بدنه تابع است. در ادامه یک مثال ساده از یک Arrow Function که یک آرگومان دریافت میکند را مشاهده میکنیم:
const greet = name => { console.log(`Hello, ${name}!`); };
ما میتوانیم در صورت تمایل آرگومان را داخل پرانتز قرار دهیم:
const greet = (name) => { console.log(`Hello, ${name}!`); }
اگر یک Arrow Function بیش از یک آرگومان داشته باشد، استفاده از پرانتز الزامی است. مانند توابع معمولی، نام آرگومانها با کاما از هم جدا میشوند:
const sum = (a, b) => { return a + b; }
یک anonymous arrow function نامی ندارد و معمولاً به عنوان تابع callback استفاده میشود:
button.addEventListener('click', event => { console.log('You clicked the button!'); });
اگر بدنه Arrow Function شامل یک statement منفرد باشد، نیازی به
{}
نداریم:
const greet = name => console.log(`Hello, ${name}!`);
یکی از تفاوتهای مهم بین Arrow Function و یک تابع معمولی در جاوااسکریپت، مفهوم بازگشت ضمنی (Implicit Return) است. در این روش، مقدار تابع بدون نیاز به نوشتن
return
، به طور خودکار بازگردانده میشود.
اگر
{}
را در یک Arrow Function حذف کنیم، مقدار عبارت داخل بدنه تابع به طور خودکار بازگردانده میشود. به عنوان مثال، تابع sum
را که قبلاً نوشتیم، دوباره با این روش بازنویسی میکنیم:
const sum = (a, b) => a + b;
بازگشت ضمنی بسیار مفید است، بهویژه هنگام تعریف توابع callback. برای مثال:
const values = [1, 2, 3]; const doubled values = values.map(value => value * 2); // [2, 4, 6]
در بازگشت ضمنی، میتوانیم هر نوع مقداری را return کنیم، اما برای آبجکتها یک نکته مهم وجود دارد. از آنجایی که آبجکتها در جاوااسکریپت با
{}
مشخص میشوند، اگر به طور مستقیم {}
را در تابع بنویسیم، جاوااسکریپت آن را به عنوان بدنه تابع در نظر میگیرد و مقدار undefined
برمیگرداند:
کد اشتباه:
const createUser = (name, email) => { name, email };
در این حالت، هیچ بازگشت ضمنی وجود نخواهد داشت و تابع در واقع
undefined
برمیگردد؛ زیرا، هیچ عبارت return
وجود ندارد. برای return کردن آبجکتها در Arrow Functionها، باید آنها را داخل پرانتز قرار دهیم:
کد درست:
const createUser = (name, email) => ({ name, email });
اکنون جاوااسکریپت میداند که این یک بازگشت ضمنی از یک آبجکت است که حاوی ویژگیهای
name
و email
میباشد.
درست مانند توابع معمولی، Arrow Functionها نیز میتوانند به صورت صریح، یعنی با استفاده از
return
مقداری را بازگردانند:
const createUser = (name, email) => { return { name, email }; };
علاوه بر سینتکس سادهتر، Arrow Functionها تفاوتهای مهمی با توابع معمولی دارند که در ادامه به بررسی آنها میپردازیم.
مهمترین تفاوت Arrow Functionها این است که برخلاف توابع معمولی، یک
this
جدید ایجاد نمیکنند. بلکه مقدار this
را از scope بیرونی به ارث میبرند. مثلا:
const counter = { value: 0, increment: () => { this.value += 1; } };
در این مثال،
this
در تابع increment
به counter
اشاره نمیکند؛ بلکه this
را از scope بیرونی (که در این مثال آبجکت window سراسری است) به ارث میبرد. به همین دلیل مقدار counter.value
تغییر نمیکند و مقدار undefined
خواهد بود.
گاهی این ویژگی مفید است، مثلاً هنگام استفاده از توابع callback. در گذشته، برای حفظ مقدار
this
درون توابع، از bind
استفاده میکردیم:
روش قدیمی:
var self = this; setTimeout(function() { console.log(self.name); }, ۱۰۰۰);
روش جدید با استفاده از Arrow Functionها:
setTimeout(() => console.log(this.name));
در این مثال،
this
از scope بیرونی به ارث برده میشود و نیازی به bind
نیست.
در توابع معمولی، میتوانیم از آبجکت
arguments
برای دسترسی به تمام آرگومانهای ورودی تابع استفاده کنیم. این آبجکت شبیه آرایه است و مقادیر تمام آرگومانهایی که به تابع ارسال شدهاند را در خود نگه میدارد.
به عنوان مثال، تابع
sum
که تعداد متغیری از آرگومانها را دریافت میکند:
function sum() { let total = 0; for (let i = 0; i < arguments.length; i++) { total += arguments[i]; } return total; }
میتوانیم این تابع را با هر تعداد آرگومان فراخوانی کنیم:
sum(1, 2, 3) // 6
اگر این تابع را به یک Arrow Function تبدیل کنیم، دیگر آبجکت
arguments
وجود نخواهد داشت. بنابراین، باید از سینتکس rest parameter استفاده کنیم:
const sum = (...args) => { let total = 0; for (let i = 0; i < args.length; i++) { total += args[i]; } return args; }
فراخوانی این نسخه از تابع
sum
مانند قبل خواهد بود:
sum(1, 2, 3) // 6
نکتهای که باید به آن توجه داشته باشیم این است که این سینتکس مخصوص Arrow Functionها نیست و در توابع معمولی هم میتوان از سینتکس rest parameter استفاده کرد. در جاوااسکریپت مدرن، دیگر از
arguments
خیلی کم استفاده میشود، بنابراین این تفاوت اهمیت زیادی ندارد.
توابع معمولی جاوااسکریپت دارای ویژگی
prototype
هستند. قبل از معرفی سینتکس class
، از این ویژگی به همراه کلمه کلیدی new
برای ایجاد آبجکتهایی که قابلیت نمونهسازی دارند، استفاده میشد. به عنوان مثال:
function Greeter() { } Greeter.prototype.sayHello = function(name) { console.log(`Hello, ${name}!`); }; new Greeter().sayHello('Joe'); // Hello, Joe!
اگر همین کار را با Arrow Functionها انجام دهیم، با خطا مواجه خواهیم شد. زیرا، Arrow Functionها
prototype
ندارند:
const Greeter = () => {}; Greeter.prototype.sayHello = name => console.log(`Hello, ${name}!`); // TypeError: Cannot set properties of undefined (setting 'sayHello')
در نتیجه، از آنجایی که Arrow Functionها دارای prototype نیستند، نمیتوانیم از آنها برای ایجاد کلاسها یا آبجکتهایی که قابلیت نمونهسازی دارند، استفاده کنیم. برای این کار، بهتر است از توابع معمولی یا کلاسها استفاده نماییم.
Arrow Functionها در بسیاری از سناریوها کاربرد دارند، اما در برخی موارد باید از توابع معمولی استفاده کنیم. این موارد عبارتند از:
زمانهایی که باید از توابع معمولی استفاده کنیم:
this
برای دسترسی به خود آبجکت استفاده کنیمthis
را صراحتاً با Function.prototype.bind
متصل کنیمyield
هستنداما Arrow Functionها در جاهایی مثل توابع callback به شدت کارآمدند، چون سینتکس کوتاهتری دارند و کد را خواناتر میکنند. به طور خاص، برای متدهای آرایهای مثل
forEach
، map
و filter
بسیار مفیدند. میتوانیم از آنها در متدهای آبجکت هم استفاده کنیم، اما فقط اگر متد نیاز نداشته باشد this
را برای دسترسی به آبجکت استفاده کند.
Arrow Functionها در شرایط خاص بسیار مفید هستند. اما مانند بسیاری از موارد دیگر، اگر به درستی از آنها استفاده نکنیم، ممکن است باعث ایجاد مشکلاتی در برنامه ما شوند.
در برخی موارد، میتوانیم متدهای یک کلاس را با Arrow Functionها تعریف کنیم. مثلا:
class Person { constructor(name) { this.name = name; } greet = () => console.log(`Hello, ${this.name}!`); }
برخلاف متدهای معمولی در یک آبجکت literal، اینجا
greet
مقدار this
را از کلاس Person
به ارث میبرد. این یعنی فارغ از اینکه متد چگونه فراخوانی شود، مقدار this
همیشه به نمونه کلاس اشاره میکند. این مثال را در نظر میگیریم که از یک متد استاندارد با setTimeout
استفاده میکند:
class Person { constructor(name) { this.name = name; } greet() { console.log(`Hello, ${this.name}!`); } delayedGreet() { setTimeout(this.greet, 1000); } } new Person('Joe').delayedGreet(); // Hello, undefined!
وقتی متد
greet
از طریق setTimeout
اجرا میشود، مقدار this
به آبجکت window اشاره میکند.name
وجود ندارد، نتیجه Hello, undefined!
خواهد شد.
اگر
greet
را به عنوان یک Arrow Function تعریف کنیم، this
مقدار کلاس Person
را حفظ میکند، حتی اگر متد در setTimeout
اجرا شود:
class Person { constructor(name) { this.name = name; } greet = () => console.log(`Hello, ${this.name}!`); delayedGreet() { setTimeout(this.greet, 1000); } } new Person('Joe').delayedGreet(); // Hello, Joe!
با Arrow Function، مقدار
this
از Person
حفظ میشود و متد greet
همچنان به نام فرد دسترسی خواهد داشت.
اما نمیتوانیم constructor را با Arrow Function بنویسیم؛ اگر بخواهیم این کار را انجام دهیم، با خطا مواجه خواهیم شد:
class Person { constructor = name => { this.name = name; } } // SyntaxError: Classes may not have a field named 'constructor'
از زمان معرفی استاندارد ES2015، برنامهنویسان جاوااسکریپت به Arrow Functionها دسترسی دارند. مزیت اصلی آنها سینتکس مختصر و خوانا است؛ یعنی دیگر نیازی به استفاده از
function
نیست و در مواردی با بازگرداندن مقدار ضمنی، نیازی به نوشتن return
هم نخواهد بود.
نبود مقدار
this
اختصاصی میتواند در برخی موارد چالشبرانگیز باشد، اما در سناریوهایی که نیاز به حفظ مقدار this
از scope بالاتر داریم، این ویژگی میتواند بسیار مفید باشد.
مثال: استفاده از Arrow Function برای سادهسازی کد
کدی که داریم یک زنجیره عملیاتی روی آرایه است که با توابع معمولی نوشته شده است:
const numbers = [1, 2, 3, 4] .map(function(n) { return n * 3; }) .filter(function(n) { return n % 2 === 0; });
با استفاده از Arrow Function کد بسیار تمیزتر و خواناتر میشود:
const numbers = [1, 2, 3, 4] .map(n => n * 3) .filter(n => n % 2 === 0);
Arrow Functionها فاقد آبجکت
arguments
هستند، اما میتوانیم از سینتکس rest parameter برای دریافت تعداد متغیر از آرگومانها استفاده کنیم.
بهطور کلی، Arrow Functionها ابزار بسیار مفیدی در جاوااسکریپت هستند، اما اگر به درستی استفاده نشوند، ممکن است مشکلاتی ایجاد کنند. بنابرین، لازم است که براساس نیازهای پروژهای که داریم بین Arrow Function و تابع معمولی انتخاب نماییم.
دیدگاهها: