همانطور که میدانیم، یک تابع در جاوااسکریپت یک مقدار است.
هر مقداری در جاوااسکریپت نوع دارد. تابع از چه نوعی است؟
در جاوااسکریپت، تابعها شیء هستند.
یک راه خوب برای تصور کردن تابعها، فکر کردن به آنها به عنوان «شیءهای عملکردی» قابل فراخوانی است. ما نه تنها میتوانیم آنها را فرا بخوانیم بلکه میتوانیم با آنها مانند شیءها رفتار کنیم: ویژگیها را اضافه/حذف کنیم، آنها را توسط مرجع رد و بدل کنیم و غیره.
ویژگی “name”
شیء تابعها چند ویژگی قابل استفاده دارند.
برای مثال، اسم یک تابع با ویژگی “name” قابل دسترس است:
function sayHi() {
alert("Hi");
}
alert(sayHi.name); // sayHi
منطق مقداردهی اسم، هوشمندانه و جالب است. حتی زمانی که یک تابع بدون اسم ساخته و سریعا تخصیص داده شود، اسم درستی را برای مقداردهی استفاده میکند:
let sayHi = function() {
alert("Hi");
};
alert(sayHi.name); // sayHi (!یک اسم دارد)
اگر مقداردهی توسط یک مقدار پیشفرض انجام شود هم کار میکند:
function f(sayHi = function() {}) {
alert(sayHi.name); // sayHi (!کار میکند)
}
f();
در مشخصات، این خاصیت «اسم زمینهای» نامیده شده است. اگر تابع اسمی نداشته باشد، سپس در مقداردهی، از زمینه موجود پیدا میشود.
متدهای شیءها هم اسم دارند:
let user = {
sayHi() {
// ...
},
sayBye: function() {
// ...
}
}
alert(user.sayHi.name); // sayHi
alert(user.sayBye.name); // sayBye
اگرچه هیچ جادویی وجود ندارد. مواردی وجود دارند که راهی برای فهمیدن اسم درست وجود ندارد. در این صورت، ویژگی اسم (name) خالی است، مثل اینجا:
// تابع درون آرایه ساخته شده است
let arr = [function() {}];
alert( arr[0].name ); // <رشته خالی>
// موتور راهی برای دریافت اسم درست ندارد، پس هیچی وجود ندارد
اگرچه در عمل، اکثر تابعها اسم دارند.
ویژگی “length”
یک ویژگی درونساخت دیگر به نام “length” وجود دارد که تعداد پارامترهای تابع را برمیگرداند، برای مثال:
function f1(a) {}
function f2(a, b) {}
function many(a, b, ...more) {}
alert(f1.length); // 1
alert(f2.length); // 2
alert(many.length); // 2
اینجا میبینیم که پارامترهای رِست شمرده نمیشوند.
ویژگی length
بعضی اوقات برای دروننگری در تابعهایی که بر روی تابعهای دیگر کاری انجام میدهند استفاده میشود.
برای مثال، در کد زیر تابع ask
یک question
(سوال) برای پرسیدن و تعدادی تابع handler
(کنترلکننده) برای فراخوانی دریافت میکند.
زمانی که کاربر جواب خود را وارد کرد، تابع کنترلکنندهها را فراخوانی میکند. ما میتوانیم دو نوع کنترلکننده را رد کنیم:
- یک تابع با صفر آرگومان که فقط زمانی که کاربر یک جواب مثبت میدهد فراخوانی شود.
- یک تابع با چند آرگومان که در هر شرایطی فراخوانی میشود و یک جواب برمیگرداند.
برای اینکه handler
را به درستی فراخوانی کنیم، ویژگی handler.length
را بررسی میکنیم.
ایده این است که ما یک سینتکس کنترلکننده ساده و بدون آرگومان برای موارد مثبت داریم (نوعی که بیشتر اتفاق میافتد) اما میتوانیم کنترلکنندههای کلی را هم پوشش دهیم:
function ask(question, ...handlers) {
let isYes = confirm(question);
for(let handler of handlers) {
if (handler.length == 0) {
if (isYes) handler();
} else {
handler(isYes);
}
}
}
// برای جواب مثبت، هر دو کنترلکننده فراخوانی میشوند
// برای جواب منفی، فقط دومی
ask("سوال؟", () => alert('شما بله گفتید'), result => alert(result));
این یک مورد استفاده از چندریختی است – رفتار متفاوت با آرگومانها با توجه به نوع آنها یا در این مورد ما با توجه به length
. این ایده در کتابخانههای جاوااسکریپت استفاده میشود.
ویژگیهای سفارشی
ما میتوانیم ویژگیهایی از خودمان را هم اضافه کنیم.
اینجا میتوانیم ویژگی counter
را اضافه کنیم تا تعداد تمام فراخوانیها را پیگیری کنیم:
function sayHi() {
alert("سلام");
// بیایید تعداد اجرا کردن را بشماریم
sayHi.counter++;
}
sayHi.counter = 0; // مقدار اولیه
sayHi(); // سلام
sayHi(); // سلام
alert( `${sayHi.counter} بار فراخوانی شد` ); // دو بار فراخوانی شد
یک ویژگی که به یک تابع تخصیص داده شود مانند sayHi.counter = 0
، متغیر محلی counter
را درون آن تعریف نمیکند. به عبارتی دیگر، یک ویژگی counter
و متغیر let counter
دو چیز غیر مرتبط هستند.
ما میتوانیم با یک تابع به عنوان یک شیء رفتار کنیم، ویژگیهایی را درون آن ذخیره کنیم اما این موضوع روی اجرا شدن آن هیچ تاثیری ندارد. متغیرها هیچوقت از ویژگیهای تابع استفاده نمیکنند و برعکس. اینها فقط دنیاهای موازی هستند.
ویژگیهای تابع میتوانند بعضی اوقات جایگزین کلوژرها شوند. برای مثال، ما میتوانیم مثال تابع شمارنده را از فصل محدوده متغیر، کلوژِر بازنویسی کنیم تا از ویژگی تابع استفاده کند:
function makeCounter() {
// :به جای این
// let count = 0
function counter() {
return counter.count++;
};
counter.count = 0;
return counter;
}
let counter = makeCounter();
alert( counter() ); // 0
alert( counter() ); // 1
count
حالا در به صورت مستقیم در خود تابع ذخیره شده است نه در محیط لغوی بیرونی آن.
این روشِ استفاده از کلوژر بهتر است یا بدتر؟
تفاوت اصلی این است که اگر مقدار count
در یک متغیر بیرونی وجود داشته باشد، سپس کد بیرونی نمیتواند به آن دسترسی داشته باشد. تنها تابعهای تودرتو ممکن است آن را تغییر دهند. و اگر فقط به یک تابع متصل باشد، سپس چنین چیزی امکان دارد:
function makeCounter() {
function counter() {
return counter.count++;
};
counter.count = 0;
return counter;
}
let counter = makeCounter();
counter.count = 10;
alert( counter() ); // 10
پس انتخاب نحوه پیادهسازی به اهداف ما بستگی دارد.
Function Expression نامگذاری شده
Function Expression نامگذاری شده، یا NFE، یک عبارت برای Function Expressionهایی است که یک اسم دارند.
برای مثال، بیایید یک Function Expression معمولی را فرض کنیم:
let sayHi = function(who) {
alert(`${who} سلام،`);
};
و یک اسم به آن بدهیم:
let sayHi = function func(who) {
alert(`سلام، ${who}`);
};
آیا ما اینجا چیزی بدست آوردیم؟ هدف اسم اضافی "func"
چیست؟
در ابتدا بیایید این را در نظر بگیریم که ما هنوز هم یک Function Expression داریم. اضافه کردن اسم "func"
بعد از function
آن را تبدیل به Function Declaration نکرد چون هنوز هم به عنوان بخشی از یک مقداردهی ساخته شده است.
اضافه کردن چنین اسمی چیزی را خراب نکرد.
تابع هنوز هم با sayHi()
قابل دسترس است:
let sayHi = function func(who) {
alert(`سلام، ${who}`);
};
sayHi("John"); // John ،سلام
دو چیز خاص درباره اسم func
وجود دارد که دلیلهایی برای آن داریم:
- این اسم به تابع اجازه میدهد که به صورت درونی به خودش رجوع کند.
- این اسم بیرون از تابع قابل رویت نیست.
برای مثال، تابع sayHi
پایین اگر هیچ مقداری برای who
تعیین نشود، خودش را با "Guest"
صدا میزند:
let sayHi = function func(who) {
if (who) {
alert(`سلام، ${who}`);
} else {
func("Guest"); // از تابع برای اینکه خودش را دوباره صدا بزند استفاده کنید
}
};
sayHi(); // Guest ،سلام
// :اما این کار نخواهد کرد
func(); // تعریف نشده است (بیرون از تابع قابل رویت نیست) func ،ارور
چرا ما از func
استفاده میکنیم؟ شاید فقط از sayHi
برای فراخوانی تودرتو باید استفاده کنیم؟
در واقع، در اکثر موارد ما میتوانیم این کار را انجام دهیم:
let sayHi = function(who) {
if (who) {
alert(`سلام، ${who}`);
} else {
sayHi("Guest");
}
};
مشکل این کد، امکان تغییر sayHi
در کد بیرونی است. اگر تابع به یک متغیر دیگر تخصیص داده شود، کد شروع به ایجاد ارور میکند:
let sayHi = function(who) {
if (who) {
alert(`سلام، ${who}`);
} else {
sayHi("Guest"); // تابع نیست sayHi :ارور
}
};
let welcome = sayHi;
sayHi = null;
welcome(); // !دیگر کار نمیکند sayHi ارور، فراخوانی تودرتوی
دلیل بروز ارور این است که تابع sayHi
را از محیط لغوی بیرونی دریافت میکند. هیچ sayHi
محلی وجود ندارد پس متغیر بیرونی استفاده میشود. و در زمان فراخوانی sayHi
بیرونی برابر با null
است.
اسم اختیاری که ما میتوانیم در Function Expression قرار میدهیم قرار است که دقیقا این دسته از مشکلات را حل کند.
بیایید از آن برای رفع مشکل کد خود استفاده کنیم:
let sayHi = function func(who) {
if (who) {
alert(`سلام، ${who}`);
} else {
func("Guest"); // حالا همه چیز درست است
}
};
let welcome = sayHi;
sayHi = null;
welcome(); // (فراخوانی تودرتو کار میکند) Guest ،سلام
حالا کار میکند چون اسم "func"
یک تابع محلی است. این اسم از بیرون دریافت نمیشود (و آنجا هم قابل رویت نیست). مشخصات زبان تضمین میکند که این اسم همیشه به تابع کنونی رجوع میکند.
کد بیرونی هنوز هم متغیر sayHi
یا welcome
خود را دارد. و func
یک «اسم درونی تابع» است، جوری که تابع میتوانند از درون خودش را فراخوانی کند.
خصوصیت «اسم درونی» که اینجا توضیح داده شد فقط برای Function Expessionها قابل استفاده است نه برای Function Declarationها. برای Function Declarationها، سینتکسی برای اضاف کردن اسم «درونی» وجود ندارد.
بعضیاوقات، نیاز به یک اسم درونی قابل، دلیلی برای نوشتن دوبارهی یک Function Declaration به Function Expression نامگذاریشده است.
خلاصه
تابعها شیء هستند.
اینجا ما ویژگیهای آنها را پوشش دادیم:
name
– اسم تابع. نه تنها در تعریف یک تابع وجود دارد، بلکه در مقداردهیها و ویژگیهای شیء هم موجود است.length
– تعداد آرگومانها در تعریف تابع. پارامترهای رست شمرده نمیشوند.
اگر تابعی به عنوان Function Expression تعریف شود (نه در جریان اصلی کد)، و اسمی داشته باشد، سپس به آن یک Function Expression نامگذاری شده میگویند. اسم میتواند درون آن برای رجوع به خودش استفاده شود، مثلا برای فراخوانیهای بازگشتی یا چنین چیزی.
همچنین، تابعها ممکن است ویژگیهای اضافی هم داشته باشند. تعداد زیادی از کتابخانههای شناختهشدهی جاوااسکریپت از این خاصیت خیلی استفاده میکنند.
آنها یک تابع «اصلی» میسازند و تعداد زیادی از تابعهای «کمکی» را به آن متصل میکنند. برای مثال، کتابخانه jQuery یک تابع به نام $
میسازد. کتابخانه lodash یک تابع _
میسازد و سپس ویژگیهای _.clone
، _keyBy
و بقیه ویژگیها را به آن اضافه میکند (زمانی که میخواهید درباره آنها بیشتر بدانید، مستندات را ببینید). در واقع، آنها این کار را برای کاهش آلودگی فضای گلوبال انجام میدهند تا یک کتابخانه فقط یک متغیر گلوبال داشته باشد. این باعث کاهش احتمال وقوع تناقض در نامگذاری میشود.
پس یک تابع، میتواند توسط خودش یک کار مفید انجام دهد و همچنین چند عملکرد مختلف را در ویژگیهایش داشته باشد.