برنامه نویسی 1554 بازدید

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

پیش‌نیازها

  • سواد ابتدایی رایانه
  • درکی اولیه از HTML و CSS
  • آشنایی مقدماتی با جاوا اسکریپت

هدف این مقاله درک مفاهیم بنیادی تشکیل دهنده تابع‌های جاوا اسکریپت است. ضمناً قسمت قبلی این مطلب را می‌توانید از طریق لینک زیر مطالعه کنید:

تابع‌ها کجا استفاده می‌شوند؟

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

هر زمان که از ساختاری دارای پرانتز در جاوا اسکریپت استفاده کنید و از ساختارهای داخلی این زبان مانند حلقه‌های for، حلقه‌های while یا do…while و یا گزاره‌های if…else استفاده نکنید؛ در واقع از تابع استفاده کرده‌اید.

تابع‌های درونی مرورگر

ما در این نوشته به طور مکرر از تابع‌های درونی تعریف شده داخل مرورگر استفاده خواهیم کرد. برای نمونه هر زمان که به صورت زیر یک رشته متن را دستکاری می‌کنیم:

var myText = 'I am a string';
var newString = myText.replace('string', 'sausage');
console.log(newString);
// the replace() string function takes a string,
// replaces one substring with another, and returns
// a new string with the replacement made

یا هر بار که یک آرایه را دستکاری می‌کنیم:

var myArray = ['I', 'love', 'chocolate', 'frogs'];
var madeAString = myArray.join(' ');
console.log(madeAString);
// the join() function takes an array, joins
// all the array items together into a single
// string, and returns this new string

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

var myNumber = Math.random();
// the random() function generates a random
// number between 0 and 1, and returns that
// number

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

زبان برنامه‌نویسی جاوا اسکریپت، تابع‌های درونی زیادی دارد که امکان اجرای کارهای مفیدی را بدون نیاز به کدنویسی دستی فراهم ساخته است. در واقع برخی از کدهایی که در هنگام اجرای یک تابع داخلی مرورگر فراخوانی می‌کنید را نمی‌توان در جاوا اسکریپت نوشت، چون بسیاری از این کدها، بخش‌های مختلفی را در پس‌زمینه مرورگر فراخوانی می‌کنند که عموماً در یک زبان سیستمی در سطح پایین‌تر مانند ++C نوشته شده‌اند و از جنس زبان برنامه‌نویسی وب مانند جاوا اسکریپت نیستند.

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

تابع‌ها در برابر متدها

نکته‌ای که پیش از ادامه این راهنما باید روشن کنیم این است که تابع‌های درونی مرورگر، به بیان فنی تابع محسوب نمی‌شوند. کلمات تابع و متد به طور گسترده و دست‌کم از نظر مقاصدی که ما در این مرحله از یادگیری جاوا اسکریپت داریم، قابل تعویض هستند.

تفاوت در این است که متدها، تابع‌هایی هستند که درون اشیا تعریف می‌شوند. تابع‌های درونی مرورگر (متدها) و متغیرها (که مشخصات یا properties نامیده می‌شوند) درون اشیای ساخت‌یافته ذخیره می‌شوند تا کد کارآمدتر باشد و مدیریت آسان‌تر شود.

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

تابع‌های سفارشی

تا به اینجا در این سلسله مطالب آموزشی جاوا اسکریپت تعداد زیادی از تابع‌های سفارشی را مشاهده کرده‌ایم. تابع‌ها درون کد تعریف می‌شوند و نه درون مرورگر. هر زمان که یک نام سفارشی را جلوی یک جفت پرانتز مشاهده کردید در واقع در حال مشاهده یک تابع سفارشی هستید. ما در مقاله «حلقه‌ها در جاوا اسکریپت» در مثال رسم دایره یک تابع سفارشی به نام ()draw معرفی کردیم که کد آن چنین بود:

function draw() {
  ctx.clearRect(0,0,WIDTH,HEIGHT);
  for (var i = 0; i < 100; i++) {
    ctx.beginPath();
    ctx.fillStyle = 'rgba(255,0,0,0.5)';
    ctx.arc(random(WIDTH), random(HEIGHT), random(50), 0, 2 * Math.PI);
    ctx.fill();
  }
}

این تابع 100 دایره سفارشی را درون یک عنصر <canvas> رسم می‌کند. هر بار که بخواهیم این کار را بکنیم کافی است تابع را به صورت زیر فراخوانی کنیم:

draw();

بدین ترتیب به جای این که مجبور باشیم همه کدهایی که لازم است را یک بار دیگر بنویسیم کافی است از یک کد کوتاه برای فراخوانی تابع استفاده کنیم. تابع‌ها می‌توانند شامل هر چیزی باشند و حتی می‌توان تابع‌های دیگر را درون یک تابع فراخوانی کرد. برای مثال، تابع فوق، تابع ()random را سه بار فراخوانی می‌کند که به وسیله کد زیر تعریف شده است:

function random(number) {
  return Math.floor(Math.random()*number);
}

ما به این تابع نیاز داریم چون تابع ()Math.random درونی مرورگر تنها اعداد اعشاری تصادفی بین 0 و 1 را تولید می‌کند و ما یک عدد کامل بین 0 و عدد مفروض خود می‌خواهیم.

فراخوانی تابع‌ها

احتمالاً تا به اینجا در مورد فراخوانی تابع‌ها اطلاعات کافی کسب کرده‌اید؛ اما جهت اطلاع توضیح می‌دهیم که پس از این که تابعی تعریف شد می‌توانیم آن را فراخوانی یا اجرا کنیم این کار از طریق گنجاندن نام آن در کدی به صورت زیر و با دو پرانتز در جلویش صورت می‌گیرد:

function myFunction() {
  alert('hello');
}

myFunction()
// calls the function once

تابع‌های بی‌نام (Anonymous)

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

function myFunction() {
  alert('hello');
}

اما شما می‌توانید تابعی داشته باشید که نام نداشته باشد:

function() {
  alert('hello');
}

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

var myButton = document.querySelector('button');

myButton.onclick = function() {
  alert('hello');
}

مثال فوق برای اجرا به یک عنصر <button> روی صفحه نیاز دارد که انتخاب شده و کلیک شود. ما این ساختار را قبلاً چند بار در طی سری مطالب قبلی این آموزش‌ها مشاهده کرده‌ایم و در مقالات بعدی نیز در مورد آن بیشتر خواهیم دانست. همچنین می‌توان از تابع بدون نام برای تعیین مقدار یک متغیر برای نمونه به ترتیب زیر استفاده کرد:

var myGreeting = function() {
  alert('hello');
}

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

myGreeting();

این کد عملاً به تابع ما یک نام می‌دهد و می‌توان این تابع را بار دیگر به مقدار چند متغیر مانند زیر انتساب داد:

var anotherGreeting = function() {
  alert('hello');
}

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

myGreeting();
anotherGreeting();

اما این کار موجب سردرگمی می‌شود و از این رو باید از آن اجتناب کنید. بهتر است که صرفاً از شکل زیر استفاده کنید:

function myGreeting() {
  alert('hello');
}

از تابع‌های بدون نام به طور عمده صرفاً برای اجرای قطعه کدهایی در پاسخ به یک اجرای رویداد مانند کلیک شدن یک دکمه و به کمک یک «دستگیره رویداد» (event handler) استفاده می‌شود. این کد به صورت زیر خواهد بود:

myButton.onclick = function() {
  alert('hello');
  // I can put as much code
  // inside here as I want
}

پارامترهای تابع

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

برای نمونه تابع درونی مرورگر به نام ()Math.random به هیچ پارامتری نیاز ندارد. زمانی که آن را فراخوانی کنید یک عدد تصادفی بین 0 و 1 بازگشت می‌دهد.

var myNumber = Math.random();

با این وجود، تابع دستکاری رشته درونی مرورگر به نام ()replace نیازمند دو پارامتر است که یکی زیر رشته‌ای است که باید در رشته اصلی پیدا شود و دیگری زیر رشته‌ای است که باید به جای زیر رشته قبلی جایگزین شود:

var myText = 'I am a string';
var newString = myText.replace('string', 'sausage');

زمانی که لازم باشد چندین پارامتر را تعیین کنیم، آن‌ها را با کاما از هم جدا می‌کنیم.

همچنین لازم به ذکر است که برخی اوقات پارامترها اختیاری هستند و لازم نیست آن‌ها را ذکر کنیم. اگر این کار را نکنیم، تابع به طور کلی نوعی رفتار پیش‌فرض را اختیار می‌کند. به عنوان مثال، پارامتر تابع ()join در مورد آرایه‌ها اختیاری است:

var myArray = ['I', 'love', 'chocolate', 'frogs'];
var madeAString = myArray.join(' ');
// returns 'I love chocolate frogs'
var madeAString = myArray.join();
// returns 'I,love,chocolate,frogs'

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

دامنه تابع و تعارض‌ها

در این بخش کمی در مورد دامنه یا حیطه (scope) تابع صحبت می‌کنیم. زمانی که با تابع‌ها سر و کار داریم این مفهوم بسیار حائز اهمیت است. هنگامی که یک تابع ایجاد می‌کنید، متغیرها و دیگر چیزهایی که درون تابع تعریف می‌شوند دارای دامنه مجزای خود هستند، یعنی درون محفظه‌های خاص خود قرار گرفته‌اند و از درون تابع‌های دیگر یا هر جایی خارج از کد قابل دسترسی نیستند.

اولین سطح خارج از تابع به نام «دامنه سراسری» (global scope) شناخته می‌شود. مقادیری که در دامنه سراسری تعریف شده‌اند از هر جایی در کد قابل دسترسی هستند.

جاوا اسکریپت به دلایل مختلف به این صورت تنظیم شده است؛ اما دلیل اصلی امنیت و سازماندهی کد است. برخی اوقات ما نمی‌خواهیم متغیرهایمان از هر جایی در کد قابل دسترسی باشند، چون در این صورت مثلاً اسکریپت‌های بیرونی که از جای دیگر فراخوانی می‌شوند، می‌توانند شروع به خرابکاری در کد بکنند و موجب بروز مشکلاتی بشوند، زیرا اتفاقاً از همان نام‌های متغیری استفاده می‌کنند که شما در بخشی از کد تعریف کرده‌اید و بدین ترتیب موجب ایجاد تعارض می‌شوند. این وضعیت می‌تواند ناشی از نیت‌های خرابکارانه و یا صرفاً بر حسب تصادف باشد.

برای نمونه، فرض کنید یک فایل HTML داریم که در دو فایل جاوا اسکریپت بیرونی فراخوانی می‌شود و هر دوی آن‌ها یک متغیر دارند و یک تابع تعریف شده که از همین نام استفاده می‌کند:

<!-- Excerpt from my HTML -->
<script src="first.js"></script>
<script src="second.js"></script>
<script>
  greeting();
</script>
// first.js
var name = 'Chris';
function greeting() {
  alert('Hello ' + name + ': welcome to our company.');
}
// second.js
var name = 'Zaptec';
function greeting() {
  alert('Our company is called ' + name + '.');
}

هر دو تابعی که فراخوانی می‌شوند ()greeting نام دارند؛ اما شما تنها می‌توانید به تابع ()greeting که در فایل second.js تعریف شده دسترسی داشته باشید، چون در زمان متأخرتری در فایل HTML قرار گرفته است و از این رو متغیر و تابع مربوط به first.js را بازنویسی می‌کند.

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

این وضعیت تا حدودی شبیه به یک باغ‌وحش است. شیرها، گورخرها، ببرها و پنگوئن‌ها هر کدام در محوطه‌هایشان جدا از هم نگه‌داری می‌شوند. دامنه‌های تابع را نیز می‌توان به این صورت تصور کرد. اگر آن‌ها بتوانند وارد قفس‌های همدیگر شوند، مشکلاتی بروز می‌یابد. در بهترین حالت حیوانات مختلف با عادت‌های متفاوت در کنار هم احساس ناراحتی خواهند داشت. مثلاً یک شیر یا ببر درون محوطه آبی و یخی پنگوئن‌ها خوشحال نخواهند بود. در بدترین حالت، شیرها و ببرها ممکن است تلاش کنند پنگوئن‌ها را بخورند!

مسئول باغ‌وحش مانند دامنه سراسری است و کلیدهای دسترسی به همه محوطه‌ها را دارد تا وظایفی مانند غذا دادن یا درمان بیماری‌ها و غیره را اجرا کند.

یادگیری عملی – کار با دامنه‌ها

در مثالی که در ادامه می‌بینید مفهوم دامنه‌بندی را بررسی می‌کنیم. ابتدا یک کپی محلی از کد زیر روی سیستم خود ایجاد کنید:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Function scope example</title>
</head>
<body>
  
  <script>
    var x = 1;
    function a() {
      var y = 2;
    }
    function b() {
      var z = 3;
    }
    function output(value) {
      var para = document.createElement('p');
      document.body.appendChild(para);
      para.textContent = 'Value: ' + value;
    }
  </script>
</body>
</html>

این کد شامل دو تابع به نام‌های ()a و ()b است و سه متغیر به نام‌های x، y و z دارد. دو مورد از آن‌ها درون تابع‌ها تعریف شده‌اند و یکی دارای دامنه سراسری است. کد فوق همچنین شامل یک تابع سوم به نام ()output است که یک پارامتر منفرد می‌گیرد و یک پاراگراف در خروجی صفحه ارائه می‌کند.

سپس فایل HTML مثال فوق را در یک مرورگر و همچنین در ویرایشگر متنی خود باز کنید. کنسول جاوا اسکریپت مرورگر خود (CTRL + SHIFT + K در فایرفاکس و CTRL + SHIFT + J در کروم) را باز کنید و دستور زیر را در آن وارد کنید:

output(x);

در این حالت می‌بینید که مقدار متغیر x روی صفحه ظاهر می‌شود. در ادامه تلاش کنید دستورهای زیر را در کنسول وارد کنید:

output(y);
output(z);

هر دو دستور فوق خطایی با مضمون «ReferenceError: y is not defined» بازگشت می‌دهند، چون دامنه تابع چنان است که y و z درون تابع‌های ()a و ()b هستند و از این رو ()output که از دامنه سراسری فراخوانی می‌شود، نمی‌تواند به آن‌ها دسترسی داشته باشد.

شاید از خود بپرسید اگر این متغیرها را از درون تابع دیگر فراخوانی کنیم چه اتفاقی رخ می‌دهد؟ برای پاسخ به این سؤال، تابع‌های ()a و ()b را به صورت زیر بازنویسی کنید:

function a() {
  var y = 2;
  output(y);
}

function b() {
  var z = 3;
  output(z);
}

کد را ذخیره کرده و صفحه را رفرش کنید. سپس تلاش کنید تابع‌های ()a و ()b را از کنسول جاوا اسکریپت فراخوانی کنید:

a();
b();

اینک می‌بینید که مقادیر y و z در صفحه ارائه می‌شوند. این کد به درستی کار می‌کند، چون تابع ()output از درون تابع‌های دیگر فراخوانی می‌شود که دارای همان دامنه متغیرها هستند. ()output خودش از هر کجا در دسترس است، چون در دامنه سراسری تعریف شده است.

اینک کد را به صورت زیر به‌روزرسانی کنید:

function a() {
  var y = 2;
  output(x);
}

function b() {
  var z = 3;
  output(x);
}

یک بار دیگر فایل را ذخیره و صفحه را رفرش کنید و دستور زیر را در کنسول وارد نمایید:

a();
b();

این بار فراخوانی ()a و ()b هر دو با خطای «ReferenceError: z is not defined» مواجه می‌شوند. دلیل این امر آن است که ()output فراخوانی می‌شود و متغیرهایی که قرار است نمایش دهد درون همان دامنه‌های تابع تعریف‌ نشده‌اند. متغیرها در عمل برای این تابع‌ها ناپیدا هستند.

نکته: حلقه‌ها مانند {…} ()for و بلوک‌های شرطی مانند {…} ()if از این مفهوم دامنه‌بندی پیروی نمی‌کنند و با این که ظاهرشان شباهت دارد؛ اما یکسان نیستند. دقت کنید که این موارد را با هم اشتباه نکنید.

نکته: خطای «ReferenceError: x is not defined» یکی از رایج‌ترین خطاهایی است که با آن مواجه خواهید شد. اگر این خطا را دریافت کردید، مطمئن باشید که متغیرها را به درستی تعریف کرده‌اید و دامنه‌ای که در آن اجرا می‌شوند را بررسی کنید.

تابع‌ها درون تابع‌ها

به خاطر داشته باشید که می‌توانید یک تابع را از هر کجا حتی از درون تابع‌های دیگر فراخوانی کنید. این وضعیت به طور معمول برای کوچک نگه‌داشتن کد استفاده می‌شود. اگر تابع بسیار پیچیده و بزرگی داشته باشید، برای درک ساده‌تر آن بهتر است که تابع را به چند تابع فرعی تقسیم کنید:

function myBigFunction() {
  var myValue;

  subFunction1();
  subFunction2();
  subFunction3();
}

function subFunction1() {
  console.log(myValue);
}

function subFunction2() {
  console.log(myValue);
}

function subFunction3() {
  console.log(myValue);
}

فقط باید اطمینان پیدا کنید که مقادیر مورد استفاده درون تابع در دامنه صحیحی قرار دارند. مثال فوق یک خطای ReferenceError: myValue is not defined ایجاد می‌کند، زیرا با این که متغیر myValue در همان دامنه‌ای تعریف شده که تابع فراخوانی می‌شود؛ اما درون تعاریف تابع تعریف‌ نشده است یعنی در کد واقعی که هنگام فراخوانی تابع اجرا می‌شود تعریف نشده است. برای این که این وضعیت اصلاح شود، باید مقدار را مانند زیر به صورت پارامتر به درون تابع ارسال کنیم:

function myBigFunction() {
  var myValue = 1;
      
  subFunction1(myValue);
  subFunction2(myValue);
  subFunction3(myValue);
}

function subFunction1(value) {
  console.log(value);
}

function subFunction2(value) {
  console.log(value);
}

function subFunction3(value) {
  console.log(value);
}

سخن پایانی

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

برای مطالعه قسمت بعدی این مطلب روی لینک زیر کلیک کنید:

اگر این مطلب برای شما مفید بوده است، آموزش‌های زیر نیز به شما پیشنهاد می‌شوند:

==

بر اساس رای 3 نفر
آیا این مطلب برای شما مفید بود؟
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.

«میثم لطفی» در رشته‌های ریاضیات کاربردی و مهندسی کامپیوتر به تحصیل پرداخته و شیفته فناوری است. وی در حال حاضر علاوه بر پیگیری علاقه‌مندی‌هایش در رشته‌های برنامه‌نویسی، کپی‌رایتینگ و محتوای چندرسانه‌ای، در زمینه نگارش مقالاتی با محوریت نرم‌افزار با مجله فرادرس همکاری دارد.

یک نظر ثبت شده در “مفهوم و کاربرد تابع در جاوا اسکریپت — به زبان ساده

  • سلام.
    من مفهوم وجود function های موجود در متدهایی مثل setTimeout(function(){
    و یا then(function(){}
    رو در نمیکنم، چرا وقتی setTimeout که خودش یک متد هست باید داخلش function نوشته بشه!؟ و اصولاً این نوع function های بدون نام چکاره اند؟

نظر شما چیست؟

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *