۲۵ ژوئیه ۲۰۲۲

اعلان تابع Expression

در جاوااسکریپت، تابع یک “ساختار جادویی زبان” نیست، بلکه یک نوع خاصی از مقدار است.

سینتکسی که ما قبلا استفاده کردیم یک Function Declaration نامیده می‌شود:

function sayHi() {
  alert( "سلام" );
}

یک سینتکس دیگر هم برای ساخت تابع وجود دارد که Function Expression نامیده می‌شود.

این سینتکس به ما این امکان را می‌دهد که بین هر عبارتی یک تابع جدید بسازیم.

برای مثال:

let sayHi = function() {
  alert( "سلام" );
};

اینجا می‌توانیم متغیر sayHi را ببینیم که مقداری را دریافت می‌کند، تابع جدید، که به صورت function() { alert("Hello"); } ساخته شده است.

چون ایجاد تابع با زمینه (context) عبارت مقداردهی (سمت راست =) رخ می‌دهد، این یک Function Expression است.

لطفا در نظر داشته باشید، هیچ اسمی بعد از کلمه کلیدی function وجود ندارد. حذف کردن اسم برای Function Expressionها مجاز است.

اینجا ما بلافاصله آن را به متغیر اختصاص می‌دهیم پس معنی این قطعه‌های کد یکسان است: «تابعی بساز و آن را درون متغیر sayHi قرار بده».

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

تابع یک مقدار است

بیایید تکرار کنیم: مهم نیست که تابع چگونه ساخته شده است، یک تابع همیشه یک مقدار است. هر دو مثال بالا تابعی را درون متغیر sayHi ذخیره می‌کنند.

ما حتی می‌توانیم آن مقدار را با استفاده از alert چاپ کنیم:

function sayHi() {
  alert( "سلام" );
}

alert( sayHi ); // کد تابع را نشان می‌دهد

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

در جاوااسکریپت، تابع یک مقدار است، پس ما می‌توانیم مثل یک مقدار با آن رفتار کنیم. کد بالا نمایش رشته‌ای آن را انجام می‌دهد، که همان کد منبع است.

مسلما، تابع یک مقدار خاص است، به همین دلیل ما می‌توانیم آن را مثل sayHi() صدا بزنیم.

اما تابع همچنان یک مقدار است. پس ما می‌توانیم با آن مثل انواع دیگر مقدارها کار کنیم.

ما می‌توانیم یک تابع را در یک متغیر دیگر کپی کنیم:

function sayHi() {   // (1) ساختن
  alert( "سلام" );
}

let func = sayHi;    // (2) کپی کردن

func(); // سلام     // (3) کپی را اجرا می‌کنیم (کار می‌کند!)
sayHi(); // سلام    //     هنوزم کار می‌کند (چرا نکند)

چیزی که بالا اتفاق می‌افتد با جزییات اینجا هست:

  1. Function Declaration (1) تابع را می‌سازد و آن را داخل متغیر sayHi قرار می‌دهد.
  2. خط (2) آن را داخل متغیر func کپی می‌کند. لطفا دوباره در نظر داشته باشید: هیچ پرانتزی بعد از sayHi وجود ندارد. اگر وجود داشت، سپس func = sayHi() نتیجه صدا زدن sayHi() را در func می‌نوشت، نه خود تابع sayHi.
  3. حالا تابع می‌تواند با sayHi() و func() صدا زده شود.

همچنین می‌توانستیم از یک Function Expression برای تعریف sayHi در خط اول، استفاده کنیم:

let sayHi = function() { // (1) ایجاد
  alert( "سلام" );
};

let func = sayHi;
// ...

همه چیز به همان شکل کار خواهد کرد.

چرا یک نقطه ویرگول در انتها وجود دارد؟

شاید برای شما سوال باشد، چرا Function Expression در انتها نقطه ویرگول ; دارد، اما Function Declaration ندارد:

function sayHi() {
  // ...
}

let sayHi = function() {
  // ...
};

جواب ساده است: یک Function Expression به صورت function(…) {…}، در داخل دستور مقداردهی استفاده می‌شود: let sayHi = ...;. نقطه ویرگول ; در انتهای دستور پیشنهاد می‌شود، این علامت جزء سینتکس تابع نیست.

نقطه ویرگول برای مقداردهی ساده‌تر وجود دارد مثل let sayHi = 5; و همچنین برای مقداردهی تابع نیز وجود دارد.

تابع‌های Callback

بیایید به مثال‌های بیشتری درباره استفاده کردن از تابع ها به عنوان مقدار و استفاده کردن از function expressions نگاه کنیم.

ما یک تابع ask(question, yes, no) با سه پارامتر می‌نویسیم:

question
متن سوال
yes
تابعی برای اجرا کردن اگر جواب “Yes” باشد
no
تابعی برای اجرا کردن اگر جواب “No” باشد

تابع باید question را بپرسد، و بر اساس جواب کاربر، yes() یا no() را صدا بزند:

function ask(question, yes, no) {
  if (confirm(question)) yes()
  else no();
}

function showOk() {
  alert( "شما موافقت کردید." );
}

function showCancel() {
  alert( "شما اجرا شدن را لغو کردید." );
}

// نحوه استفاده: تابع‌های showOk، showCancel به عنوان آرگومان به ask داده شده‌اند
ask("آیا موافق هستید؟", showOk, showCancel);

در عمل، چنین تابع هایی بسیار مفید هستند. تفاوت اصلی بین یک ask در زندگی واقعی و مثال بالا در این است که تابع‌ها در زندگی واقعی از راه‌های پیچیده‌تری نسبت به یک confirm ساده برای تعامل با کاربر استفاده می‌کنند. در مرورگر، چنین تابع‌هایی معمولا یک پنچره پرسش زیبا را ایجاد می‌کنند. اما آن یک داستان دیگر است.

آرگومان‌های showOk و showCancel داخل ask callback functions یا فقط callbacks نامیده می‌شوند.

ایده اینطور است که ما یک تابع را می‌دهیم و از آن توقع داریم که بعدا اگر نیاز شد “دوباره صدا زده شود”. در مورد ما، showOk تبدیل به callback برای جواب “yes” می‌شود، و showCancel برای چواب “no”.

ما می توانیم از Function Expressions برای نوشتن همان تابع به صورت بسیار کوتاه‌تر استفاده کنیم:

function ask(question, yes, no) {
  if (confirm(question)) yes()
  else no();
}

ask(
  "آیا موافق هستید؟",
  function() { alert("شما موافقت کردید."); },
  function() { alert("شما اجرا شدن را لغو کردید."); }
);

اینجا، تابع‌ها دقیقا درون صدا زدن ask(...) تعریف شده اند. آنها هیچ اسمی ندارند، و بنابراین anonymous نامیده می شود. چنین تابع هایی بیرون از ask قابل دسترسی نیستند (چون آنها به متغیری تخصیص داده نشده اند)، اما این چیزی است که ما اینجا می‌خواهیم.

چنین کدی در اسکریپت‌های ما به طور طبیعی نمایان می شوند، این در ذات جاوااسکریپت است.

یک تابع مقداری است که یک “عمل” را نمایش می‌دهد

مقدارهای معمولی مثل رشته‌ها یا عددها داده را نمایش می‌دهند.

یک تابع می‌تواند به عنوان یک عمل درک شود.

ما می‌توانیم آن را بین متغیرها رد و بدل کنیم و هر زمان که بخواهیم اجرا کنیم.

اعلان تابع Expression در مقابل تابع Declaration

بیایید تفاوت‌های کلیدی بین Function Declarations و Expressions را فرمول بندی کنیم.

اول، سینتکس: چگونه داخل کد بین آنها فرق قائل شویم.

  • Function Declaration: یک تابع است، که به عنوان یک دستور جدا، در کد اصلی تعریف می‌شود.

    // Function Declaration
    function sum(a, b) {
      return a + b;
    }
  • Function Expression: یک تابع است، که در داخل یک عبارت یا داخل یک ساختار سینتکس دیگر ساخته می‌شود. اینجا، تابع سمت راست “عبارت تخصیص دادن” = ساخته شده است.

    // Function Expression
    let sum = function(a, b) {
      return a + b;
    };

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

یک Function Expression زمانی ساخته می‌شود که اجرا شدن به آن می‌رسد و فقط از همان لحظه قابل استفاده است.

بلافاصله که جریان اجرا شدن به سمت راست تخصیص دادن let sum = function... برسد – د برو که رفتیم، تابع از آن لحظه ساخته شده و می‌تواند استفاده شود (تخصیص داده شود، صدا زده شود، و…).

Function Declarations متفاوت هستند.

یک Function Declaration می‌تواند زودتر از زمانی که تعریف شده باشد صدا شده شود.

برای مثال، یک Function Declaration سراسری داخل کل اسکریپت قابل رویت است، هیچ فرقی ندارد که کجا باشد.

این به دلیل الگوریتم‌های داخلی است. زمانی که جاوااسکریپت برای اجرای اسکریپت آماده می‌شود، اول به دنبال Function Declarationهای سراسری می‌گردد و تابع‌ها را می‌سازد. ما می‌توانیم به عنوان یک “مرحله مقداردهی اولیه” به آن فکر کنیم.

و بعد از اینکه همه Function Declarations پردازش شدند، کد اجرا می‌شود. پس به این تابع‌ها دسترسی دارد.

برای مثال، این کار می‌کند:

sayHi("John"); // سلام، John

function sayHi(name) {
  alert( `سلام، ${name}` );
}

Function Declaration sayHi زمانی که جاوااسکریپت برای شروع اسکریپت در حال آماده شدن است ساخته می‌شود و هرجایی داخل آن قابل رویت است.

…اگر این یک Function Expression بود، سپس کار نمی‌کرد:

sayHi("John"); // ارور!

let sayHi = function(name) {  // (*) دیگر جادویی وجود ندارد
  alert( `سلام، ${name}` );
};

Function Expressions زمانی که اجرا شدن به آنها می‌رسد ساخته می‌شوند. این فقط در خط (*) اتفاق می‌افتد. خیلی دیر است.

یکی دیگر از ویژگی‌های Function Declaration ویژگی block scope آنها است.

در حالت سختگیرانه(strict mode)، زمانی که یک Function Declaration داخل یک بلوک کد است، همه جای آن بلوک قابل رویت است. اما نه خارج از آن.

برای مثال، بیایید تصور کنیم که می‌خواهیم یک تابع welcome() تعریف کنیم که به متغیر age بستگی دارد که آن را زمان اجرا دریافت می‌کنیم. و سپس می‌خواهیم از آن بعدا استفاده کنیم.

اگر ما از Function Declaration استفاده کنیم، آن طور که در نظر داریم کار نمی‌کند:

let age = prompt("سن شما چقدر است؟", 18);

// بر اساس شرط یک تابع تعریف کن
if (age < 18) {

  function welcome() {
    alert("سلام!");
  }

} else {

  function welcome() {
    alert("درود!");
  }

}

// ...بعدا از آن استفاده کن
welcome(); // ارور: welcome تعریف نشده است

دلیل آن این است که یک Function Declaration فقط داخل بلوک کدی که داخل آن مستقر است قابل رویت است.

اینجا یک مثال دیگر داریم:

let age = 16; // 16 را به عنوان یک مثال دریافت کنید

if (age < 18) {
  welcome();               // \   (اجرا می‌شود)
                           //  |
  function welcome() {     //  |
    alert("سلام!");         //  |  Function Declaration در دسترس است
  }                        //  |  هرجایی از بلوکی که داخل آن تعریف شده است
                           //  |
  welcome();               // /   (اجرا می‌شود)

} else {

  function welcome() {
    alert("درود!");
  }
}

// اینجا ما بیرون از آکولادها هستیم،
// پس ما نمی‌توانیم Function Declarationهایی که داخل آنها ساخته شده‌اند را رویت کنیم.

welcome(); // ارور: welcome تعریف نشده است

ما چه کاری می‌توانیم انجام دهیم تا welcome را بیرون از if قابل رویت کنیم؟

رویکرد درست می‌تواند این باشد که از Function Expression استفاده کنیم و welcome را به متغیری تخصیص بدهیم که خارج از if تعریف شده باشد و قابل رویت باشد.

این کد به طوری که در نظر داریم کار می‌کند:

let age = prompt("سن شما چقدر است؟", 18);

let welcome;

if (age < 18) {

  welcome = function() {
    alert("سلام!");
  };

} else {

  welcome = function() {
    alert("درود!");
  };

}

welcome(); // الان درست است

یا حتی ما می‌توانیم آن را با استفاده از عملگر علامت سوال ? ساده‌تر کنیم:

let age = prompt("سن شما چقدر است؟", 18);

let welcome = (age < 18) ?
  function() { alert("سلام!"); } :
  function() { alert("درود!"); };

welcome(); // الان درست است
چه زمانی Function Declaration را انتخاب کنیم و چه زمانی Function Expression؟

به عنوان یک قاعده کلی، زمانی که ما نیاز به تعریف یک تابع داریم، اولین چیزی که باید سراغ آن برویم سینتکس Function Declaration است. آن به ما آزادی بیشتری برای سازماندهی کردن کد مان به ما می‌دهد، چون ما می‌توانیم چنین تابع‌هایی را قبل از اینکه تعریف شوند صدا بزنیم.

همچنین آن برای خوانایی نیز بهتر است، چون پیدا کردن function f(...) {...} در کد راحت تر است از let f = function(...) {...}. Function Declarationها “چشم نوازتر” هستند.

…اما اگر یک Function Declaration برای ما به دلایلی مناسب نبود، یا ما یک تعریف بر اساس شرط نیاز داشتیم (که به تازگی یک مثال از آن دیدیم)، سپس Function Expression باید استفاده شود.

خلاصه

  • تابع‌ها مقدار هستند. آنها می‌توانند هرجای کد تخصیص داده شوند، کپی شوند یا تعریف شوند.
  • اگر تابع به عنوان یک دستور جداگانه در جریان کد اصلی تعریف شده باشد، یک “Function Declaration” نامیده می‌شود.
  • اگر تابع به عنوان بخشی از یک عبارت ساخته شده باشد، یک “Function Expression” نامیده می‌شود.
  • Function Declarations قبل از اینکه بلوک کد اجرا شود پردازش می‌شوند. آنها از هرجای بلوک قابل رویت هستند.
  • Function Expressions زمانی که جریان اصلی به آنها می‌رسد ساخته می‌شوند.

در اکثر موارد زمانی که ما می‌خواهیم یک تابع تعریف کنیم، یک Function Declaration ترجیح داده می‌شود، چون قبل از اینکه تعریف شود قابل رویت است. آن به ما انعطاف بیشتری برای سازماندهی کد می‌دهد، و معمولا خواناتر است.

پس ما باید فقط زمانی از Function Expression استفاده کنیم که Function Declaration برای کار مناسب نباشد. ما یک جفت مثال از آن در این فصل دیدیم، و در آینده بیشتر خواهیم دید.

نقشه آموزش