۵۰ درصد تخفیف ویژه برای همه دوره‌ها تا پایان امروز

استفاده از Arrow Function در جاوااسکریپت

Arrow Function در جاوااسکریپت با استاندارد ES2015 معرفی شده است و نسبت به توابع معمولی سینتکس ساده‌تری دارد، اما همان‌طور که در این مقاله خواهیم دید، تفاوت‌های مهمی در نحوه عملکرد آن با توابع سنتی وجود دارد.

Arrow Function در جاوااسکریپت چیست؟

در حالی که arrow functionها تقریباً در هر جایی که یک تابع معمولی استفاده می‌شود، قابل استفاده هستند، چند استثنا نیز وجود دارد. این توابع سینتکسی فشرده دارند و مانند توابع معمولی، شامل لیستی از آرگومان‌ها، بدنه تابع و مقدار بازگشتی احتمالی می‌باشند.

در ادامه، جزئیات Arrow Functionها را بررسی خواهیم کرد، اما به‌طور کلی، زمانی که نیاز به یک binding جدید برای

this
this داریم، نباید از آن‌ها استفاده کنیم. Arrow Functionها
this
this مخصوص به خود ندارند و مقدار
this
this را از scope بیرونی خود به ارث می‌برند.

همچنین، از Arrow Functionها نمی‌توانیم به عنوان constructor یا تابع generator استفاده کنیم، زیرا این توابع نمی‌توانند حاوی دستور

yield
yield باشند.

بررسی سینتکس بیسیک یک Arrow Function در جاوااسکریپت

یک Arrow Function در جاوااسکریپت شامل لیستی از آرگومان‌ها، یک فلش (

=>
=>) و بدنه تابع است. در ادامه یک مثال ساده از یک Arrow Function که یک آرگومان دریافت می‌کند را مشاهده می‌کنیم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const greet = name => {
console.log(`Hello, ${name}!`);
};
const greet = name => { console.log(`Hello, ${name}!`); };
const greet = name => {
  console.log(`Hello, ${name}!`);
};

ما می‌توانیم در صورت تمایل آرگومان را داخل پرانتز قرار دهیم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const greet = (name) => {
console.log(`Hello, ${name}!`);
}
const greet = (name) => { console.log(`Hello, ${name}!`); }
const greet = (name) => {
  console.log(`Hello, ${name}!`);
}

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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const sum = (a, b) => {
return a + b;
}
const sum = (a, b) => { return a + b; }
const sum = (a, b) => {
  return a + b;
}

یک anonymous arrow function نامی ندارد و معمولاً به عنوان تابع callback استفاده می‌شود:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
button.addEventListener('click', event => {
console.log('You clicked the button!');
});
button.addEventListener('click', event => { console.log('You clicked the button!'); });
button.addEventListener('click', event => {
  console.log('You clicked the button!');
});

اگر بدنه Arrow Function شامل یک statement منفرد باشد، نیازی به

{}
{} نداریم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const greet = name => console.log(`Hello, ${name}!`);
const greet = name => console.log(`Hello, ${name}!`);
const greet = name => console.log(`Hello, ${name}!`);

بازگشت ضمنی در Arrow Function جاوااسکریپت

یکی از تفاوت‌های مهم بین Arrow Function و یک تابع معمولی در جاوااسکریپت، مفهوم بازگشت ضمنی (Implicit Return) است. در این روش، مقدار تابع بدون نیاز به نوشتن

return
return، به طور خودکار بازگردانده می‌شود.

اگر

{}
{} را در یک Arrow Function حذف کنیم، مقدار عبارت داخل بدنه تابع به طور خودکار بازگردانده می‌شود. به عنوان مثال، تابع
sum
sum را که قبلاً نوشتیم، دوباره با این روش بازنویسی می‌کنیم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const sum = (a, b) => a + b;
const sum = (a, b) => a + b;
const sum = (a, b) => a + b;

بازگشت ضمنی بسیار مفید است، به‌ویژه هنگام تعریف توابع callback. برای مثال:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const values = [1, 2, 3];
const doubled values = values.map(value => value * 2); // [2, 4, 6]
const values = [1, 2, 3]; const doubled values = values.map(value => value * 2); // [2, 4, 6]
const values = [1, 2, 3];
const doubled values = values.map(value => value * 2); // [2, 4, 6]

بازگرداندن یک آبجکت به صورت ضمنی

در بازگشت ضمنی، می‌توانیم هر نوع مقداری را return کنیم، اما برای آبجکت‌ها یک نکته مهم وجود دارد. از آنجایی که آبجکت‌ها در جاوااسکریپت با

{}
{} مشخص می‌شوند، اگر به طور مستقیم
{}
{} را در تابع بنویسیم، جاوااسکریپت آن را به عنوان بدنه تابع در نظر می‌گیرد و مقدار
undefined
undefined برمی‌گرداند:

کد اشتباه:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const createUser = (name, email) => { name, email };
const createUser = (name, email) => { name, email };
const createUser = (name, email) => { name, email };

در این حالت، هیچ بازگشت ضمنی وجود نخواهد داشت و تابع در واقع

undefined
undefined برمی‌گردد؛ زیرا، هیچ عبارت
return
return وجود ندارد. برای return کردن آبجکت‌ها در Arrow Functionها، باید آن‌ها را داخل پرانتز قرار دهیم:

کد درست:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const createUser = (name, email) => ({ name, email });
const createUser = (name, email) => ({ name, email });
const createUser = (name, email) => ({ name, email });

اکنون جاوااسکریپت می‌داند که این یک بازگشت ضمنی از یک آبجکت است که حاوی ویژگی‌های

name
name و
email
email می‌باشد.

بازگشت صریح (Explicit Return) در Arrow Functionها

درست مانند توابع معمولی، Arrow Functionها نیز می‌توانند به صورت صریح، یعنی با استفاده از

return
return مقداری را بازگردانند:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const createUser = (name, email) => {
return { name, email };
};
const createUser = (name, email) => { return { name, email }; };
const createUser = (name, email) => {
  return { name, email };
};

تفاوت‌های Arrow Functionها و توابع معمولی در جاوااسکریپت

علاوه بر سینتکس ساده‌تر، Arrow Functionها تفاوت‌های مهمی با توابع معمولی دارند که در ادامه به بررسی آن‌ها می‌پردازیم.

عدم ایجاد this جدید

مهم‌ترین تفاوت Arrow Functionها این است که برخلاف توابع معمولی، یک

this
this جدید ایجاد نمی‌کنند. بلکه مقدار
this
this را از scope بیرونی به ارث می‌برند. مثلا:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const counter = {
value: 0,
increment: () => {
this.value += 1;
}
};
const counter = { value: 0, increment: () => { this.value += 1; } };
const counter = {
  value: 0,
  increment: () => {
    this.value += 1;
  }
};

در این مثال،

this
this در تابع
increment
increment به
counter
counter اشاره نمی‌کند؛ بلکه
this
this را از scope بیرونی (که در این مثال آبجکت window سراسری است) به ارث می‌برد. به همین دلیل مقدار
counter.value
counter.value تغییر نمی‌کند و مقدار
undefined
undefined خواهد بود.

گاهی این ویژگی مفید است، مثلاً هنگام استفاده از توابع callback. در گذشته، برای حفظ مقدار

this
this درون توابع، از
bind
bind استفاده می‌کردیم:

روش قدیمی:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
var self = this;
setTimeout(function() {
console.log(self.name);
}, ۱۰۰۰);
var self = this; setTimeout(function() { console.log(self.name); }, ۱۰۰۰);
var self = this;
setTimeout(function() {
  console.log(self.name);
}, ۱۰۰۰);

روش جدید با استفاده از Arrow Functionها:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
setTimeout(() => console.log(this.name));
setTimeout(() => console.log(this.name));
setTimeout(() => console.log(this.name));

در این مثال،

this
this از scope بیرونی به ارث برده می‌شود و نیازی به
bind
bind نیست.

عدم وجود آبجکت arguments

در توابع معمولی، می‌توانیم از آبجکت

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

به عنوان مثال، تابع

sum
sum که تعداد متغیری از آرگومان‌ها را دریافت می‌کند:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
function sum() {
let total = 0;
for (let i = 0; i < arguments.length; i++) {
total += arguments[i];
}
return total;
}
function sum() { let total = 0; for (let i = 0; i < arguments.length; i++) { total += arguments[i]; } return total; }
function sum() {
  let total = 0;
  for (let i = 0; i < arguments.length; i++) {
    total += arguments[i];
  }

  return total;
}

می‌توانیم این تابع را با هر تعداد آرگومان فراخوانی کنیم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
sum(1, 2, 3) // 6
sum(1, 2, 3) // 6
sum(1, 2, 3) // 6

چرا arguments در Arrow Functionها وجود ندارد؟

اگر این تابع را به یک Arrow Function تبدیل کنیم، دیگر آبجکت

arguments
arguments وجود نخواهد داشت. بنابراین، باید از سینتکس rest parameter استفاده کنیم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const sum = (...args) => {
let total = 0;
for (let i = 0; i < args.length; i++) {
total += args[i];
}
return args;
}
const sum = (...args) => { let total = 0; for (let i = 0; i < args.length; i++) { total += args[i]; } return args; }
const sum = (...args) => {
  let total = 0;
  for (let i = 0; i < args.length; i++) {
    total += args[i];
  }

  return args;
}

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

sum
sum مانند قبل خواهد بود:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
sum(1, 2, 3) // 6
sum(1, 2, 3) // 6
sum(1, 2, 3) // 6

نکته‌ای که باید به آن توجه داشته باشیم این است که این سینتکس مخصوص Arrow Functionها نیست و در توابع معمولی هم می‌توان از سینتکس rest parameter استفاده کرد. در جاوااسکریپت مدرن، دیگر از

arguments
arguments خیلی کم استفاده می‌شود، بنابراین این تفاوت اهمیت زیادی ندارد.

عدم وجود prototype

توابع معمولی جاوااسکریپت دارای ویژگی

prototype
prototype هستند. قبل از معرفی سینتکس
class
class، از این ویژگی به همراه کلمه کلیدی
new
new برای ایجاد آبجکت‌هایی که قابلیت نمونه‌سازی دارند، استفاده می‌شد. به عنوان مثال:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
function Greeter() { }
Greeter.prototype.sayHello = function(name) {
console.log(`Hello, ${name}!`);
};
new Greeter().sayHello('Joe'); // Hello, Joe!
function Greeter() { } Greeter.prototype.sayHello = function(name) { console.log(`Hello, ${name}!`); }; new Greeter().sayHello('Joe'); // Hello, Joe!
function Greeter() { }
Greeter.prototype.sayHello = function(name) {
  console.log(`Hello, ${name}!`);
};

new Greeter().sayHello('Joe'); // Hello, Joe!

اگر همین کار را با Arrow Functionها انجام دهیم، با خطا مواجه خواهیم شد. زیرا، Arrow Functionها

prototype
prototype ندارند:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const Greeter = () => {};
Greeter.prototype.sayHello = name => console.log(`Hello, ${name}!`);
// TypeError: Cannot set properties of undefined (setting 'sayHello')
const Greeter = () => {}; Greeter.prototype.sayHello = name => console.log(`Hello, ${name}!`); // TypeError: Cannot set properties of undefined (setting 'sayHello')
const Greeter = () => {};
Greeter.prototype.sayHello = name => console.log(`Hello, ${name}!`);
// TypeError: Cannot set properties of undefined (setting 'sayHello')

در نتیجه، از آنجایی که Arrow Functionها دارای prototype نیستند، نمی‌توانیم از آن‌ها برای ایجاد کلاس‌ها یا آبجکت‌هایی که قابلیت نمونه‌سازی دارند، استفاده کنیم. برای این کار، بهتر است از توابع معمولی یا کلاس‌ها استفاده نماییم.

چه زمانی از باید Arrow Functionها استفاده کنیم و چه زمانی از توابع معمولی؟

Arrow Functionها در بسیاری از سناریوها کاربرد دارند، اما در برخی موارد باید از توابع معمولی استفاده کنیم. این موارد عبارتند از:

زمان‌هایی که باید از توابع معمولی استفاده کنیم:

  1. در constructor یک کلاس
  2. در متدهای یک آبجکت، زمانی که نیاز داریم از
    this
    this برای دسترسی به خود آبجکت استفاده کنیم
  3. در توابعی که باید
    this
    this را صراحتاً با
    Function.prototype.bind
    Function.prototype.bind متصل کنیم
  4. در توابع generator که شامل
    yield
    yield هستند

اما Arrow Functionها در جاهایی مثل توابع callback به شدت کارآمدند، چون سینتکس کوتاه‌تری دارند و کد را خواناتر می‌کنند. به طور خاص، برای متدهای آرایه‌ای مثل

forEach
forEach،
map
map و
filter
filter بسیار مفیدند. می‌توانیم از آن‌ها در متدهای آبجکت هم استفاده کنیم، اما فقط اگر متد نیاز نداشته باشد
this
this را برای دسترسی به آبجکت استفاده کند.

Arrow Functionها در شرایط خاص بسیار مفید هستند. اما مانند بسیاری از موارد دیگر، اگر به درستی از آن‌ها استفاده نکنیم، ممکن است باعث ایجاد مشکلاتی در برنامه ما شوند.

چگونه یک متد را با استفاده از Arrow Function تعریف کنیم؟

در برخی موارد، می‌توانیم متدهای یک کلاس را با Arrow Functionها تعریف کنیم. مثلا:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
class Person {
constructor(name) {
this.name = name;
}
greet = () => console.log(`Hello, ${this.name}!`);
}
class Person { constructor(name) { this.name = name; } greet = () => console.log(`Hello, ${this.name}!`); }
class Person {
  constructor(name) {
    this.name = name;
  }

  greet = () => console.log(`Hello, ${this.name}!`);
}

برخلاف متدهای معمولی در یک آبجکت literal، اینجا

greet
greet مقدار
this
this را از کلاس
Person
Person به ارث می‌برد. این یعنی فارغ از اینکه متد چگونه فراخوانی شود، مقدار
this
this همیشه به نمونه کلاس اشاره می‌کند. این مثال را در نظر می‌گیریم که از یک متد استاندارد با
setTimeout
setTimeout
 استفاده می‌کند:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
class Person {
constructor(name) {
this.name = name;
}
greet() {
console.log(`Hello, ${this.name}!`);
}
delayedGreet() {
setTimeout(this.greet, 1000);
}
}
new Person('Joe').delayedGreet(); // Hello, undefined!
class Person { constructor(name) { this.name = name; } greet() { console.log(`Hello, ${this.name}!`); } delayedGreet() { setTimeout(this.greet, 1000); } } new Person('Joe').delayedGreet(); // Hello, undefined!
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
greet از طریق
setTimeout
setTimeout اجرا می‌شود، مقدار
this
this به آبجکت window اشاره می‌کند.
از آنجایی که در window ویژگی به نام
name
name وجود ندارد، نتیجه‌
Hello, undefined!
Hello, undefined! خواهد شد.

اگر

greet
greet را به عنوان یک Arrow Function تعریف کنیم،
this
this مقدار کلاس
Person
Person را حفظ می‌کند، حتی اگر متد در
setTimeout
setTimeout
اجرا شود:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
class Person {
constructor(name) {
this.name = name;
}
greet = () => console.log(`Hello, ${this.name}!`);
delayedGreet() {
setTimeout(this.greet, 1000);
}
}
new Person('Joe').delayedGreet(); // Hello, Joe!
class Person { constructor(name) { this.name = name; } greet = () => console.log(`Hello, ${this.name}!`); delayedGreet() { setTimeout(this.greet, 1000); } } new Person('Joe').delayedGreet(); // Hello, Joe!
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
this از
Person
Person حفظ می‌شود و متد
greet
greet همچنان به نام فرد دسترسی خواهد داشت.

اما نمی‌توانیم constructor را با Arrow Function بنویسیم؛ اگر بخواهیم این کار را انجام دهیم، با خطا مواجه خواهیم شد:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
class Person {
constructor = name => {
this.name = name;
}
}
// SyntaxError: Classes may not have a field named 'constructor'
class Person { constructor = name => { this.name = name; } } // SyntaxError: Classes may not have a field named 'constructor'
class Person {
  constructor = name => {
    this.name = name;
  }
}

// SyntaxError: Classes may not have a field named 'constructor'

جمع‌بندی

از زمان معرفی استاندارد ES2015، برنامه‌نویسان جاوااسکریپت به Arrow Functionها دسترسی دارند. مزیت اصلی آن‌ها سینتکس مختصر و خوانا است؛ یعنی دیگر نیازی به استفاده از

function
function نیست و در مواردی با بازگرداندن مقدار ضمنی، نیازی به نوشتن
return
return هم نخواهد بود.

نبود مقدار

this
this اختصاصی می‌تواند در برخی موارد چالش‌برانگیز باشد، اما در سناریوهایی که نیاز به حفظ مقدار
this
this از scope بالاتر داریم، این ویژگی می‌تواند بسیار مفید باشد.

مثال: استفاده از Arrow Function برای ساده‌سازی کد

کدی که داریم یک زنجیره‌ عملیاتی روی آرایه است که با توابع معمولی نوشته شده است:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const numbers = [1, 2, 3, 4]
.map(function(n) {
return n * 3;
})
.filter(function(n) {
return n % 2 === 0;
});
const numbers = [1, 2, 3, 4] .map(function(n) { return n * 3; }) .filter(function(n) { return n % 2 === 0; });
const numbers = [1, 2, 3, 4]
  .map(function(n) {
    return n * 3;
  })
  .filter(function(n) {
    return n % 2 === 0;
  });

با استفاده از Arrow Function کد بسیار تمیزتر و خواناتر می‌شود:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const numbers = [1, 2, 3, 4]
.map(n => n * 3)
.filter(n => n % 2 === 0);
const numbers = [1, 2, 3, 4] .map(n => n * 3) .filter(n => n % 2 === 0);
const numbers = [1, 2, 3, 4]
  .map(n => n * 3)
  .filter(n => n % 2 === 0);

Arrow Functionها فاقد آبجکت

arguments
arguments هستند، اما می‌توانیم از سینتکس rest parameter برای دریافت تعداد متغیر از آرگومان‌ها استفاده کنیم.

به‌طور کلی، Arrow Functionها ابزار بسیار مفیدی در جاوااسکریپت هستند، اما اگر به درستی استفاده نشوند، ممکن است مشکلاتی ایجاد کنند. بنابرین، لازم است که براساس نیاز‌های پروژه‌ای که داریم بین Arrow Function و تابع معمولی انتخاب نماییم.

 

دیدگاه‌ها:

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