آموزش جاوا اسکریپت – جلسه ۴ – توابع [Functions]
![آموزش جاوا اسکریپت – جلسه 4 – توابع [Functions]](https://rahkarino.ir/wp-content/uploads/2020/05/js-part4-functions.jpg)
در فصل سوم، انواع عملگرهای جاوااسکریپتی را معرفی کردیم، عملگرهای شرطی، منطقی و مقایسه ای را آموزش دادیم، حلقه های for و while و do..while را معرفی کردیم و ساختار دستور switch-case را بیان کردیم.
در این مقاله قصد داریم انواع تابع در زبان برنامه نویسی جاوا اسکریپت را بهمراه نحوه تعریف هریک از آنها آموزش دهیم.
توابع (Functions):
در اغلب موارد می خواهیم دستورات ثابت و مشخصی در جاهای مختلف برنامه اجرا شود. مثلا هنگام لاگین شدن کاربر، حذف یک محصول از سبد خرید یا کاربردهای دیگر می خواهیم یک پیغام متناسب با آن کار را به کاربر نمایش دهیم. توابع این امکان را به ما می دهند که بدون اینکه کدها را تکرار کنیم، از پس اینکار بر بیاییم.
تابع با فانکشن بلاک های اصلی ساختمان یک برنامه می باشد. ما تا اینجای آموزش توابع پیش فرض بسیاری را در جاوا اسکریپت مشاهده کرده ایم. مانند: like alert(message), prompt(message, default) و confirm(question)
Function Declaration:
برای تولید یک تابع باید آن را با ساختار زیر تعریف کنیم:
function name(parameters) { ...body... }
ابتدا کلمه کلیدی function را می آوریم، سپس نام دلخواه برای تابع، سپس لیست آرگومان های ورودی تابع (در صورت وجود) که با ویرگول از هم جدا شده اند، در نهایت هم بدنه دستورات تابع را تعریف می کنیم.
بعنوان مثال در کد زیر یک تابع بنام showMessage تعریف کرده ایم که دو بار آن را فراخوانی کرده ایم:
function showMessage() { alert( 'Hello everyone!' ); } showMessage(); showMessage();
متغیرهای محلی (Local Variables):
متغیری که در بدنه یک تابع تعریف می شود در همان تابع فقط قابل شناسایی و استفاده است و خارج از آن undefined می باشد. بعنوان مثال:
function showMessage() { let message = "Hello, I'm JavaScript!"; // local variable alert( message ); } showMessage(); // Hello, I'm JavaScript! alert( message ); // <-- Error! The variable is local to the function
متغیرهای گلوبال (Global Variables):
یک تابع می تواند به متغیرهایی که خارج از تابع تعریف شده اند نیز دسترسی داشته باشد. تابع دسترسی کامل به متغیرهای گلوبال دارد و می تواند مقدار آن را نیز تغییر دهد. بعنوان مثال:
let userName = 'John'; function showMessage() { userName = "Bob"; // (1) changed the outer variable let message = 'Hello, ' + userName; alert(message); } alert( userName ); // John before the function call showMessage(); alert( userName ); // Bob, the value was modified by the function
نکته: اگر یک متغیر گلوبال (Global) با نام var1 داشته باشیم و در بدنه تابع نیز متغیری به همین نام (var1) وجود داشته باشد، اولویت با متغیر داخلی تابع است. یعنی در صورت استفاده از متغیر var1 متغیر داخلی فراخوانی خواهد شد.
به برنامه نویسان توصیه می شود که کمتر از متغیرهای گلوبال استفاده کنند. مگر در مواردی که می خواهند متغیرهای عمومی سطح برنامه (public-level) تعریف کنند.
پارامترهای تابع (Parameters):
می توان برای توابع پارامتر ورودی تعریف کرد. در مثال زیر رو پارامتر ورودی from و text تعریف شده است:
function showMessage(from, text) { // arguments: from, text alert(from + ': ' + text); } showMessage('Ann', 'Hello!'); // Ann: Hello! (*) showMessage('Ann', "What's up?"); // Ann: What's up? (**)
در کد بالا، وقتی تابع در خط های * و ** صدا زده می شود، مقادیر پارامترها در آرگومان های تابع کپی می شوند و نظیر به نظیر در متغیرهای from و text جاگذاری می شوند.
اکنون به مثال دیگری توجه کنید. در مثال زیر تابع مذکور مقدار from را تغییر می دهد اما این تغییر در خارج از تابع قابل مشاهده نیست. زیرا توابع همیشه یک کپی از پارامتر را ایجاد می کنند و روی آن تغییرات را انجام می دهند.
function showMessage(from, text) { from = '*' + from + '*'; // make "from" look nicer alert( from + ': ' + text ); } let from = "Ann"; showMessage(from, "Hello"); // *Ann*: Hello // the value of "from" is the same, the function modified a local copy alert( from ); // Ann
مقادیر پیش فرض (Default Values):
اگر پارامتر تعریف نشده باشد با خطای undefined روبرو خواهیم شد. تابع showMessage(from, text) می تواند با یک پارامتر صدا زده شود. مثلا
showMessage("Ann");
با فراخوانی تابع به صورت فوق با خطا مواجه نمی شویم. فقط مقدار پارامتر text برابر undefined خواهد شد.
همچنین می توان به شکل زیر، یک مقدار پیش فرض یا default برای پارامتر تابع تعریف کرد:
function showMessage(from, text = "no text given") { alert( from + ": " + text ); } showMessage("Ann"); // Ann: no text given
در این حالت اگر مقداری برای پارامترtext تعریف نشود مقدار پیش فرض no text given برای آن در نظر گرفته می شود.
نکته: نسخه های قدیمی جاوااسکریپت امکان تعریف مقدار پیش فرض را به شکل بالا نداشتند. بلکه باید مقادیر پیش فرض مانند زیر تعریف می شد:
function showMessage(from, text) { if (text === undefined) { text = 'no text given'; } alert( from + ": " + text ); }
یا توسط عملگر ||
function showMessage(from, text) { // if text is falsy then text gets the "default" value text = text || 'no text given'; ... }
بازگرداندن یک مقدار (Returning a value):
توابع می توانند یک مقدار را بعنوان خروجی به محل صدا زده شدن خود ارسال کنند. مثال زیر نمونه ساده ای از return است:
function sum(a, b) { return a + b; } let result = sum(1, 2); alert( result ); // 3
اما یک تابع می تواند بر اساس شرایط خاص یک مقدار مشخصی را برگرداند. در واقع می توان از چند دستور return در یک تابع استفاده کرد:
function checkAge(age) { if (age >= 18) { return true; } else { return confirm('Do you have permission from your parents?'); } } let age = prompt('How old are you?', 18); if ( checkAge(age) ) { alert( 'Access granted' ); } else { alert( 'Access denied' ); }
نکته: همچنین می توان مقابل دستور return مقداری ننویسیم. در این حالت تابع پس از رسیدن به دستور return اجرای آن متوقف می شود و خارج می شود.
function showMovie(age) { if ( !checkAge(age) ) { return; } alert( "Showing you the movie" ); // (*) // ... }
در مثال بالا اگر مقدار checkAge برابر false باشد دستور return اجرا خواهد شد و اجرای تابع متوقف می شود.
نکته: اگر تابع یک return خالی داشته باشد، یا هیچ return ای برای آن تعریف نشده باشد خروجی تابع برابر undefined خواهد بود. مثال زیر:
function doNothing() { /* empty */ } alert( doNothing() === undefined ); // true
یا مثال زیر:
function doNothing() { return; } alert( doNothing() === undefined ); // true
نکته: دقت کنید که مقدار خروجی دستور return را در خط بعدی تعریف نکنید. مانند زیر:
return (some + long + expression + or + whatever * f(a) + f(b))
در اینصورت جاوااسکریپت پس از return علامت ; را در نظر می گیرد (مانند زیر) و باعث می شود تابع خروجی نداشته باشد:
return; (some + long + expression + or + whatever * f(a) + f(b))
در واقع با تعریف دستور return به شکل فوق یک return خالی تعریف شده است.
اما اگر می خواهید مقادیر return را بصورت خط به خط و خواناتر تعریف کنید باید مانند زیر داخل پرانتز تعریف شود:
return ( some + long + expression + or + whatever * f(a) + f(b) )
نام گذاری توابع (Naming a function):
توابع اکشن هستند پس نام آنها عموما بیانگر یک فعل می باشد. نام تابع باید به گونه ای باشد که مختصر، دقیق و توصیفی باشد. در واقع با دیدن نام تابع بفهمیم آن تابع بطور کلی چکاری انجام می دهد.
روش مرسوم نامگذاری توابع استفاده از پیشوند برای نام می باشد. مثلا:
- کلمه ‘show…’: نمایش دادن چیزی
- کلمه ‘get…’: بازگرداندن یک مقدار
- کلمه ‘calc…’: محاسبه چیزی
- کلمه ‘create…’: ایجاد چیزی
- کلمه ‘check…’: چک کردن چیزی و بازگرداندن یک مقدار بولین
چند مثال از نام گذاری توابع:
showMessage(..) // shows a message getAge(..) // returns the age (gets it somehow) calcSum(..) // calculates a sum and returns the result createForm(..) // creates a form (and usually returns it) checkPermission(..) // checks a permission, returns true/false
یک تابع باید یک کار مشخصی را انجام دهد. اگر میبینید که دستورات تابع طولانی و پیچیده شدند بهتر است آن را به چندین تابع کوچکتر بخش بندی کنید. تعریف توابع کوچکتر و دقیق تر باعث می شود عملیات تست و دیباگ کردن آنها ساده تر شود.
بعنوان مثال تابع زیر اعداد اول (prime) را به خروجی می دهد:
function showPrimes(n) { nextPrime: for (let i = 2; i < n; i++) { for (let j = 2; j < i; j++) { if (i % j == 0) continue nextPrime; } alert( i ); // a prime } }
تابع زیر نیز همینکار را انجام می دهد اما در قالب دو تابع:
function showPrimes(n) { for (let i = 2; i < n; i++) { if (!isPrime(i)) continue; alert(i); // a prime } } function isPrime(n) { for (let i = 2; i < n; i++) { if ( n % i == 0) return false; } return true; }
بنظرتون فهمیدن تابع دوم راحت تر نیست؟ مسلما تابع دوم را راحت تر و سریع تر می توان فهمید.
Function Expressions:
تاکنون توابع را با ساختار زیر (Function Declaration) تعریف کرده ایم:
function sayHi() { alert( "Hello" ); }
توابع را به روش دیگری نیز می توان تعریف کرد (Function Expression):
let sayHi = function() { alert( "Hello" ); };
در کد بالا به جاوا اسکریپت گفتیم تابعی با دستور alert تولید کن و آن را در متغیری بنام sayHi درج کن.
حتی می توانیم دستورات تابع فوق را به شکل زیر نمایش دهیم:
function sayHi() { alert( "Hello" ); } alert( sayHi ); // shows the function code
نکته: دقت شود که دستور خط آخر تابع را اجرا نمی کند زیرا پس از sayHi پرانتز نوشته نشده است.
در جاوا اسکریپت توابع بعنوان متغیر در نظر گرفته می شود پس می توان با آنها مانند متغیر رفتار کنیم.
همچنین می توانیم به شکل زیر یک تابع را در نام متغیر دیگری کپی کنیم:
function sayHi() { // (1) create alert( "Hello" ); } let func = sayHi; // (2) copy func(); // Hello // (3) run the copy (it works)! sayHi(); // Hello // this still works too (why wouldn't it)
در مثال بالا ابتدا بدنه تابع تعریف شده است و در نام sayHi ذخیره شده است. در دستور بعدی دستورات موجود در sayHi را در func کپی کرده ایم (دقت کنید که هیچکدام از آنها پرانتز ندارند پس اجرا نخواهند شد). در دستور بعدی تابع func اجرا شده است که کلمه Hello چاپ می شود. به همین ترتیب در نهایت تابع sayHi اجرا شده است.
سوال: چرا در انتهای توابعی که به صورت Expression تعریف شده اند علامت ; (semicolon) تعریف می شود اما در انتهای توابع Declaration نباید از ; استفاده کرد؟
function sayHi() { // ... } let sayHi = function() { // ... };
جواب ساده است. در انتهای بلاک های کد مانند if{..} نیازی به تعریف ; نمی باشد. اما با توجه به اینکه در تعریف تابع به شکل Expression جاوا اسکریپت به صورت تابع با آن رفتار می کند (let sayHi = …;) بهتر است semicolon تعریف شود.
مقایسه Expression و Declaration:
بیایید نگاهی به تفاوت های انواع مختلف تعریف تابع در جاوا اسکریپت بیاندازیم.
تفاوت اول: تفاوت آنها از لحاظ syntax به این شکل است که:
- Function Declaration: تابع اصلی بصورت زیر تعریف می شود (در یک دستور مجزا):
// Function Declaration function sum(a, b) { return a + b; }
- Function Expression: تابع اصلی در کنار یک ساختار دیگر تعریف می شود (در مقابل یک متغیر و یک عملگر =):
// Function Expression let sum = function(a, b) { return a + b; };
تفاوت دوم: توابعی که بصورت Declaration تعریف شده باشند می توانند قبل از اینکه تعریف شوند فراخوانی شوند. مانند زیر:
sayHi("John"); // Hello, John function sayHi(name) { alert( `Hello, ${name}` ); }
در صورتی که اگر همین تابع به صورت Expression تعریف میشد تابع اجرا نمی شد:
sayHi("John"); // error! let sayHi = function(name) { // (*) no magic any more alert( `Hello, ${name}` ); };
تفاوت سوم: در strict mode اگر توابعی که به صورت Declaration تعریف شده اند در بلاک کد باشند، فقط در همان بلاک شناخته می شوند و در خارج از آن نمی توان آن ها را صدا زد. مثال زیر:
let age = prompt("What is your age?", 18); // conditionally declare a function if (age < 18) { function welcome() { alert("Hello!"); } } else { function welcome() { alert("Greetings!"); } } // ...use it later welcome(); // Error: welcome is not defined
در مثال بالا، بر اساس سن کاربر می خواهیم به او یک پیغام خوشامدگویی نمایش دهیم. تابع welcome (که به صورت declaration تعریف شده است) فقط در داخل بلاک کد خودش قابل شناسایی است و نمی توان آن را خارج از بلاکش فراخوانی کرد.
در مثال زیر، تابع welcome فقط در بلاک if شناخته شده است:
let age = 16; // take 16 as an example if (age < 18) { welcome(); // \ (runs) // | function welcome() { // | alert("Hello!"); // | Function Declaration is available } // | everywhere in the block where it's declared // | welcome(); // / (runs) } else { function welcome() { alert("Greetings!"); } } // Here we're out of curly braces, // so we can not see Function Declarations made inside of them. welcome(); // Error: welcome is not defined
اما اگر بخواهیم تابع welcome در خارج از بلاک کد if نیز قابل فراخوانی باشد چه باید کرد؟
نوع استاندارد و ایده آل تعریف تابع در این حالت تعریف تابع بصورت Expression می باشد. تابع welcome را باید به یک متغیر assign کنیم که در بالای بلاک کد if تعریف شده باشد.
کد زیر همانطور که می خواهیم بدون مشکل کار می کند:
let age = prompt("What is your age?", 18); let welcome; if (age < 18) { welcome = function() { alert("Hello!"); }; } else { welcome = function() { alert("Greetings!"); }; } welcome(); // ok now
یا حتی می توانیم کد مثال فوق را توسط عملگر شرطی ؟ تعریف کنیم تا خلاصه تر و کوتاه تر شود:
let age = prompt("What is your age?", 18); let welcome = (age < 18) ? function() { alert("Hello!"); } : function() { alert("Greetings!"); }; welcome(); // ok now
در این مثال ابتدا سن کاربر از او پرسیده می شود (با مقدار پیش فرض 18) سپس اگر سن ورودی کاربر از 18 کوچکتر باشد Hello نمایش داده می شود و اگر از 18 بزرگتر باشد Greeting
چه زمانی باید از متد Expression استفاده کنیم و چه زمانی از Declaration؟
اولین راهی که در حالت عادی پیشنهاد می کنیم تعریف تابع به صورت Declaration می باشد. زیرا آزادی عمل بیشتری خواهید داشت و نیز امکان فراخوانی تابع را قبل از تعریف دستورات آن را خواهید داشت.
علاوه بر این، تعریف توابع به روش Declaration نسبت به Expression خوانایی بیشتری دارد. مثلا تابع function f(…) {…} را راحت تر می تواند بررسی کرد نسبت به تابع let f = function(…) {…};
اما اگر روش Declaration پاسخگوی نیاز شما در نحوه تعریف تابع نبود می توانید از متد Expression استفاده کنید.
توابع Callback:
فرض کنید تابعی داریم بنام ask که 3 پارامتر question-yes-no دارد. در این تابع سوال question از کاربر پرسیده می شود. بر اساس پاسخ کاربر (yes یا no) هر یک از توابع yes() یا no() اجرا خواهند شد.
function ask(question, yes, no) { if (confirm(question)) yes() else no(); } function showOk() { alert( "You agreed." ); } function showCancel() { alert( "You canceled the execution." ); } // usage: functions showOk, showCancel are passed as arguments to ask ask("Do you agree?", showOk, showCancel);
در مثال بالا، آرگومان های showOk و showCancel توابع کال بک یا callback functions هستند. در واقع توابع کالبک توابعی هستند که انتظار داریم در طول اجرای برنامه در صورت لزوم فراخوانی شده و اجرا شوند. در مثال فوق، تابع showOk یک تابع کال بک برای پاسخ yes و تابع showCancel یک تابع کال بک برای پاسخ no
البته می توانیم مثال فوق را بصورت Function Expression تعریف کنیم. در اینصورت توابع بطور مستقیم در ask تعریف شده اند و هیچ نامی ندارند که به آنها توابع بی نامه یا anonymous گفته می شود (و به این دلیل که هیچ متغیری برای آنها تعریف نشده است خارج از ask شناخته شده نمی باشند و نمی توانند فراخوانی شوند):
function ask(question, yes, no) { if (confirm(question)) yes() else no(); } ask( "Do you agree?", function() { alert("You agreed."); }, function() { alert("You canceled the execution."); } );
مفاهیم Arrow Function:
قابلیت تعریف توابع جاوا اسکریپتی به روش Arrow از اکما اسکریپت 6 (ES-6) ارائه شد. روشی که نسبت به روش Expression بهتر است و خوانایی بیشتری دارد. ساختار کلی بصورت زیر است:
let func = (arg1, arg2, ...argN) => expression
در کد بالا یک تابع بنام func تعریف کرده ایم که چند آرگومان arg1, arg2, … را بعنوان ورودی می گیرد. دستورات تابع نیز در سمت راست فلش یا arrow تعریف می شوند.
اگر بخواهیم تابع فوق را به روش Expression تعریف کنیم داریم:
let func = function(arg1, arg2, ...argN) { return expression; };
تعریف یک تابع Arrow در یک مثال واقعی:
let sum = (a, b) => a + b; /* This arrow function is a shorter form of: let sum = function(a, b) { return a + b; }; */ alert( sum(1, 2) ); // 3
نکته: اگر ما فقط یک آرگومان ورودی داشته باشیم نیازی به تعریف پرانتز نمی باشد. مثال زیر:
let double = n => n * 2; // roughly the same as: let double = function(n) { return n * 2 } alert( double(3) ); // 6
نکته: اگر تابع ما هیچ آرگومان ورودی ندارد فقط یک پرانتز خالی می نویسیم (دقت کنید که گذاشتن پرانتز در این حالت الزامی است):
let sayHi = () => alert("Hello!"); sayHi();
Arrow Function همانند Expression Function مورد استفاده قرار میگیرد. مثلا در کد زیر، توابع بی نام را بصورت arrow ایجاد کرده ایم:
let age = prompt("What is your age?", 18); let welcome = (age < 18) ? () => alert('Hello') : () => alert("Greetings!"); welcome();
تا کنون توابعی که مثال زدیم فقط شامل یک دستور بودند. اما اگر تابع ما شامل چندین دستور باشد باید آنها را در براکت } { تعریف کنیم:
let sum = (a, b) => { // the curly brace opens a multiline function let result = a + b; return result; // if we use curly braces, then we need an explicit "return" }; alert( sum(1, 2) ); // 3
در فصل 5 از آموزش جاوا اسکریپت قصد داریم نحوه خطایابی (Debugging) کدهای جاوا اسکریپت را در مرورگر گوگل کروم آموزش دهیم.
دیدگاهتان را بنویسید