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

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

بازگشت

تابع چیست؟

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

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

تابع‌هایی که پارامتری نمی‌گیرند و مقدار بازگشتی نیز ندارند

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

function sayHello () {

console.log("Hello!");

}

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

sayHello();

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

اگر از es6/es2015 استفاده می‌کنید، در این صورت تابع فوق را می‌توانید به صورت یک تابع arrow بنویسید:

const sayHello = () => {

console.log("Hello!");

}

تابع arrow روشی خلاصه برای نوشتن یک تابع محسوب می‌شود. شیوه فراخوانی این تابع‌ها دقیقاً همانند تابع‌های معمولی است:

sayHello();

یک تابع arrow ساختار کوتاه‌تری نسبت به تابع‌های معمولی جاوا اسکریپت دارد و مقادیر this، arguments، super یا new.target را در خود ندارد. در این مورد در ادامه بیشتر توضیح خواهیم داد.

این تابع‌ها برای استفاده به عنوان تابع‌های غیر متدی بسیار مناسب هستند و نمی‌توان از آن‌ها به عنوان سازنده (constructor) استفاده کرد.

وقتی می‌گوییم تابع فوق هیچ مقداری باز نمی‌گرداند منظور ما این است که اگر بخواهیم مقدار بازگشتی تابع را در یک متغیر ذخیره کنیم، این متغیر به صورت تعریف نشده خواهد بود. برای نمونه

let message = sayHello();

// The below console.log will return undefined as the function
// doesn't return any value.

console.log (message);

تابع‌هایی که پارامتر می‌گیرند؛ اما مقداری باز نمی‌گردانند

تابع زیر یک پارامتر می‌گیرد؛ اما مقداری را بازگشت نمی‌دهد:

function log (message) {

console.log (message);

}

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

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

// The below call to log() function, logs the output to the

// and returns undefined.

log ("Hello JavaScript!");

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

تابع‌هایی که یک پارامتر می‌گیرند و یک مقدار بازمی‌گردانند

در این بخش تابعی را نمایش می‌دهیم که عددی را به عنوان پارامتر می‌پذیرد و مربع آن عدد را باز می‌گرداند.

function square(number) {

return number * number;

}
console.log(square(2));

خروجی تابع فوق در تصویر زیر مشخص است:

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

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

// You can also, use var or let. I am using const indicating

// that this function cannot be reassigned once declared.

const square = function (number) {

return number * number;

}

console.log(square(2));

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

const square = (number) => {

return number * number;

}

console.log(square(2)); // Outputs: 4

تابع می‌تواند بیش از یک آرگومان بگیرد (در واقع می‌تواند n پارامتر بگیرد)

از لحاظ نظری تعداد بیشینه‌ای برای آرگومان/پارامترهای یک تابع وجود ندارد؛ اما عملاً این تعداد محدود است. این محدودیت به نوع مرورگر و همچنین نوع تابع بستگی دارد؛ ولی جای نگرانی نیست، چون شما می‌توانید چندین هزار پارامتر را نیز در یک تابع تعریف کنید. البته چنین عددی از پارامترها، هیچ توجیهی ندارد.

چگونه تابعی بنویسیم که n پارامتر بگیرد؟

فرض کنید تابعی می‌نویسیم که n آرگومان می‌گیرد و مجموع آن‌ها را باز می‌گرداند:

// Old way

const sum = function () {

let result = 0;

for(let i = 0; i < arguments.length; i++) {

result += arguments[i];

}

return result;

}

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

console.log(sum(1,2));

console.log(sum(1,2,3,4));

console.log(sum(1,3,5,7,9));

طرز کار این تابع چگونه است؟

اگر به تابع مجموع نگاه کنید، این تابع صراحتاً هیچ پارامتری نمی‌گیرد. اینک تصور کنید، می‌خواهید این تابع ()sum را صریحاً پیاده‌سازی کنید. متوجه هستید که تعریف کردن همه این پارامترها تا چه اندازه دشوار است، چون شما نمی‌دانید که تابع ()sum با چه تعداد پارامتر فراخوانی خواهد شد.

  • این تابع می‌تواند با 1 پارامتر فراخوانی شود، یعنی (sum(1 که مقدار 1 باز می‌گرداند.
  • این تابع می‌تواند با 2 پارامتر فراخوانی شود، یعنی (sum(1,2 که مقدار 3 باز می‌گرداند.
  • این تابع می‌تواند با 100 پارامتر فراخوانی شود، یعنی (sum(1,2,3,4,5,6………..,100 که مقدار 5050 باز می‌گرداند.

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

اینک توجه کنید که شیء آرگومان، یک آرایه نیست؛ بلکه یک شیء شبه آرایه است. این بدان معنی است که شما نمی‌توانید هیچ متد آرایه‌ای را روی شیء آرگومان فراخوانی کنید.

طرز کار تابع ()sum فوق در تصویر زیر نمایش یافته است:

زمانی که می‌گوییم «arguments» یک شیء شبه آرایه‌ای است در تصویر زیر با یک فراخوانی تابع sum بهتر می‌توانیم آن را درک کنیم. نمایش کنسول آرگومان‌ها را ببینید:

در تصویر فوق کاملاً واضح است که «arguments» شیئی است که کلیدهای آن نشان‌دهنده اندیس و مقادیرش نشان‌دهنده خود پارامتر است. این شیء مانند هر شیء دیگر است، برای نمونه:

{

name: "Rajesh",

hobbies: ["writing","programming"]

}

except the keys in the 'arguments' object looks like array index as

{

0: "rajesh",

1: ["writing","programming"]

}

Both of the above are object representation.

توجه کنید که در جاوا اسکریپت مدرن استفاده از شیء «arguments» توصیه نمی‌شود و می‌توان از مفهوم دیگری که rest parameters نام دارد استفاده کرد. در ادامه روش دستیابی به نتایج بدون استفاده از شیء arguments؛ بلکه با استفاده از rest parameters را مشاهده می‌کنید:

// New way using REST parameter

const sum = function (...args) {

let result = 0;

for(let i = 0; i < args.length; i++) {

result += args[i];

}

return result;

}

همه چیز مانند تابع فوق است به جز این که آن «argument» جادویی با پارامتر REST جایگزین شده است. اینک می‌توانید آن را هر چیزی بنامید؛ اما در این مقاله آن را به بنا به عرف جاوا اسکریپت «args» می‌نامیم.

args… چه نقشی دارد؟

args… همه پارامترهای ارسال به یک تابع را می‌گیرد و آن را به صورت یک شیء آرایه واقعی درمی‌آورد. به خاطر دارید که پیش‌تر اشاره کردیم شیء «argument» یک شیء شبه آرایه است و یک آرایه واقعی نیست؛ اما args… یک آرایه واقعی است.

در ادامه مثالی از فراخوانی تابع sum ارائه می‌کنیم و خروجی را باز هم در کنسول نمایش می‌دهیم. این بار از متد reduce آرایه استفاده می‌کنیم. همان طور که قبلاً اشاره کردیم args… یک آرایه واقعی است و می‌توان از هر متدی روی آن استفاده کرد.

const sum = function (...args) {

console.log(args);

let result = 0;

result = args.reduce((current, prev) => {

return current + prev;

});

return result;

}

sum(1,2,3,4,5);

خروجی فراخوانی فوق برای تابع sum همراه با (console.log(args در تصویر زیر نمایش یافته است، زیرا مشهور است که یک تصویر بالاتر از 1000 کلمه توضیح است.

در log فوق می‌توانید به سادگی args… را به عنوان یک آرایه نمایش دهید. و از آنجا که یک آرایه است می‌توانید از متد reduce آرایه برای محاسبه مجموع استفاده کنید. از args… می‌توان به صورت جزئی نیز استفاده کرد. در انتهای این بخش مثالی برای این وضعیت ارائه می‌کنیم:

اگر تابع sum را اینک با سه پارامتر به صورت (sum(1,2, 3 فراخوانی کنیم (دقت کنید که عملاً جمع نمی‌کنیم)، خروجی به صورت زیر خواهد بود:

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

همان طور که قبلاً اشاره کردیم یک تابع شیئی از کلاس درجه اول است و از این رو می‌تواند به عنوان یک پارامتر/ آرگومان به یک تابع ارسال شود.

در ادامه تابعی می‌نویسیم که یک تابع دیگر به عنوان پارامتر می‌گیرد.

function dispatch (fn) {

fn();

}

در کد فوق یک تابع به نام dispatch تعریف می‌کنیم که یک تابع دیگر به عنوان پارامتر می‌گیرد. توجه داشته باشید که نام fn تنها یک رسم است و شما می‌توانید هر نام دیگری را انتخاب کنید. نام رایج دیگر callback است؛ اما این نام در چارچوب دیگری استفاده می‌شود.

نکته: نام callback زمانی استفاده می‌شود که به تابع‌هایی ارجاع می‌دهیم که تابع دیگر را به عنوان پارامتر می‌گیرند. اینک سؤال این است که چگونه می‌توان از تابع فوق استفاده کرد؟

فرض کنید تابع فوق را به صورتی که در ادامه نشان داده شده، فراخوانی می‌کنیم. شما می‌توانید از ساختار تابع نرمال یا تابع arrow استفاده کنید. ما از arrow استفاده می‌کنیم.

METHOD 1: Defining function as a variable and passing it.

var fn = () => { console.log("Hello!"); }

// Invoke the dispatch () function

dispatch(fn);// Outputs "Hello!"

METHOD 2: Defining an normal anonymous function inline.

dispatch (function () {

console.log("Hello!");

});

METHOD 3: Defining an arrow function inline

dispatch (() => { console.log ("Hello!") });

دقت کنید که هر 3 متد فوق یکسان هستند. تابع callback نیز می‌تواند پارامتری گرفته و مقداری بازگرداند. در ادامه مثال دیگری را مشاهده می‌کنید:

function dispatch(fn) {// Takes 'function' as an argument

return fn("hello");// You can send some parameters.

}

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

let result = dispatch(function (p1) {

return My message and ${p1};

});

کاربرد عملی تابع‌های Callback

فرض کنید می‌خواهیم یک تابع را پس از 1 ثانیه و نه بی‌درنگ فراخوانی کنیم. در این حالت می‌توانیم از تابع setTimeout استفاده کنیم:

setTimeout(function () {

console.log('Check the status of some server...');

}, 1000);

متد فوق پیش از اجرا، برای دست‌کم 1 ثانیه صبر می‌کند. دقت کنید که این زمان که برای متدهای setTimeout و setInterval سپری می‌شود به صورت میلی‌ثانیه است و کمینه زمان را نشان می‌دهد؛ اما آن را تضمین نمی‌کند.

اگر بخواهیم برخی عملیات‌ها هر 5 ثانیه یک بار اجرا شوند چه باید بکنیم. این همان جایی است که متد setInterval به کار می‌آید.

// Here I am using the arrow function notation.

setInterval(()=> {

console.log("This will be executed every 5 second");

}, 5000);

درون این تابع‌ها می‌توانید هر کدی بنویسد و حتی می‌توانید فراخوانی‌های AJAX نیز داشته باشید. دقت کنید که ما نمادگذاری تابع نرمال و arrow را با هم استفاده می‌کنیم تا چشم مخاطبان به هر دو نمادگذاری عادت کند. متد callback در زندگی روزمره در فراخوانی‌های Ajax، پیکربندی مسیرهایی برای اپلیکیشن‌ها و غیره مشاهده می‌شود.

تابع می‌تواند خودش را فراخوانی کند (بازگشت)

بازگشت (Recursion) مفهوم جذابی است که در آن یک تابع خودش را فراخوانی می‌کند. اینک اگر شرایط خاتمه تعیین نشده باشد، این تابع می‌تواند تا بی‌نهایت کار کند و در نهایت مرورگر با یک خطای سرریز پشته مانند «Maximum call stack size exceeded» مواجه می‌شود. در ادامه مفهوم بازگشت را بررسی کرده و کاربردهای مفید آن را بررسی می‌کنیم.

function runForEver() {

runForEver();

}

// You can invoke the above function by running

runForEver();// If you try you will get the call stack error

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

function countDown(n) {

console.log (n);

if (n >= 1) {// Exit or terminal condition

countDown(n-1);

}

}

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

countDown(5);// -> The output will be 5, 4, 3, 2, 1

این تابع بازگشتی چگونه کار می‌کند؟

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

let data = [
   {
      title: "menu 1",
      children: [
          { title: "menu 1.1"},
          {
             title: "menu 1.2",
             children: [
               {title: "menu 1.2.1"},
               {title: "menu 1.2.2"},
            ]
        },
     ]
  },
  {
     title: "menu 2",
     children: [
        { title: "menu 2.1"},
        { title: "menu 2.2"},
     ]
  }
]

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

تصویر سمت چپ خروجی تابع و تصویر سمت راست رندر تابع سمت چپ است.

در ادامه تابع بازگشتی را که داده‌ها را به جفت‌های <ul> فوق تبدیل می‌کند می‌نویسیم. ابتدا ببینیم که چگونه می‌توانیم از این تابع استفاده کنیم.

let uls = buildTree(data);

// Output data to console

console.log(uls);

// Render on the window

// Only for demo. In real case append it to any parent element

// instead of using document.write

document.write(uls);

اینک باید تابع buildTree را پیاده‌سازی کنیم

// Accepts two arguments

// data-> the data to transform

// isChild -> default false, used to indicate whether the node

// being rendered is a child element or not

function buildTree(data, isChild = false) {

let html = '<ul>';// Let's initial html variable

// Run the forloop over the data

data.forEach((d) => {

// For every data element render an <li>

html += <li>${d.title}</li>;

// If the current data element has children then call the

// buildTree again passing in the children and isChild = true

if (d.children) {

html += buildTree(d.children, true);

}

});

// Build the closing <ul> tag

html += '</ul>';

return html;// Return the html

}

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

استفاده بی‌درنگ از تابع فراخوانی شده (IIFE)

فرض کنید که می‌دانیم IIFE چیست و چه مشکلی را حل می‌کند. پیش از آن به بررسی تابع ناشناس می‌پردازیم. یک تابع ناشناس (anonymous function) تابعی است که نام ندارد، برای مثال:

function () {

let message = "I don't have a name";

console.log(message);

}

اینک سؤال این است که اگر تابعی نام نداشته باشد، پس چطور می‌توان آن را فراخوانی کرد؟ پاسخ این است که نمی‌توان چنین کاری کرد، مگر این که بخشی از یک پارامتر تابع callback باشد.

اینک ببینیم که چگونه می‌توان یک تابع ناشناس ساخت.

(function () {

let message = "I don't have a name";

console.log(message);

})();

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

IIFE چگونه استفاده می‌شود؟

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

نکات فنی برای افراد علاقه‌مند

همه تابع‌ها شیء هستند؛ اما همه اشیا تابع نیستند. آن چه که یک تابع را از اشیای دیگر متمایز می‌کند، این است که تابع را می‌توان فراخوانی کرد. به طور خلاصه تابع‌ها «شیءهای تابعی» هستند.

اشیا و تابع‌ها (call, apply و bind)

در ادامه نگاهی خواهیم داشت به این که شیءها و تابع‌ها چگونه می‌توانند کنار هم قرار بگیرند. هر تابع به سه متد دسترسی دارد.

  • Call
  • Apply
  • Bind

در ادامه استفاده از سه مورد فوق را با مثالی نشان می‌دهیم. این مثال‌ها روی شیء زیر استفاده می‌شوند.

let user = {
   userName: "codingmonk",
   displayName: "rajesh",
   sendMessage: function (message) {
      console.log(Sending ${message} to ${this.displayName});
   }
}
let student = {
   displayName: "rohan"
}

کد فوق دو متغیر برای نمایش شیءهای کاربر (user) و دانشجو (student) ایجاد می‌کند. ممکن است متوجه شده باشید که شیء user برخی مشخصات و متدهای اضافی دارد؛ در حالی که شیء دانشجو تنها یک مشخصه دارد و آن نیز با شیء کاربر یکسان است.

اینک می‌خواهیم ببینیم که چگونه می‌توانیم به کاربر پیامی ارسال کنیم. این کار به‌اندازه‌ای آسان است که می‌توان در یک خط آن را انجام داد:

user.sendMessage("Hello...");

و پیام «…Hello» در کنسول به صورت log ارائه می‌شود. توجه داشته باشید که this درون تابع ()sendMessage به شیئی اشاره دارد که تابع فراخوانی می‌کند که در این مورد شیء user است.

اینک چگونه می‌توانید ()sendMessage را روی شیء student فراخوانی کنید. این یک سناریوی عجیب است که در برخی جاها لازم است. این همان جایی است که عملگرهای call, apply و bind به کار می‌آیند.

استفاده از متد ()call برای فراخوانی متد روی یک شیء دیگر

user.sendMessage.call(student, “Hello from Rajesh”);

پارامتر اول برای فراخوانی متد در چارچوب جدیدی است و در این مورد دانشجو و پارامترهای بعدی آرگومان‌هایی برای sendMessage هستند که در این مثال متنی به صورت «Hello from Rajesh» است.

تابع فوق خروجی «Hello from Rajesh to rohan» را نمایش می‌دهد. اینک در sendMessage، عبارت this اشاره به شیء student دارد و نه شیء user.

استفاده از متد ()apply برای فراخوانی متد روی یک شیء دیگر

user.sendMessage.apply(student, [“Hello from Rajesh agin..”]);

پارامتر اول متد apply یک چارچوب جدید است که در این مورد student است و پارامترهای بعدی آرگومان‌هایی برای sendMessage هستند که در این مثال متن «Hello from Rajesh» است. در متد ()apply؛ آرگومان‌ها به صوت یک مدخل ارسال می‌شوند. این تنها تفاوت بین ()call و ()apply است. تابع فوق پیام «Hello from Rajesh again.. to rohan» را در خروجی نمایش می‌دهد. درون sendMessage عبارت this اینک شیء student را به جای شیء user نمایش می‌دهد.

استفاده از متد ()bind

متد bind یک متد جدید با چارچوب جدید باز می‌گرداند. برای مثال فرض کنید می‌خواهیم یک متغیر ایجاد کنیم که به ()sendMessage اشاره کند؛ اما در یک چارچوب جدید باشد.

let sendMessageToStudent = user.sendMessage.bind(student);

خط فوق یک متغیر جدید به نام sendMessageToStudent می‌سازد که در واقع یک تابع است که می‌توان آن را فراخوانی کرد؛ اما چارچوب this به شیء student اشاره می‌کند و نه شیء user. این تابع را می‌توان به صورت زیر فراخوانی کرد.

sendMessageToStudent("Yet another message");

دستور فوق متن «Sending Yet another message.. to roha» را در خروجی نمایش می‌دهد. بدین ترتیب مفاهیم call، apply و bind توضیح داده شده‌اند.

تابع سازنده (Constructor)

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

نکته: اگر یک شیء ساده مانند string یا numer یا Boolean از یک تابع سازنده بازگشت می‌دهید، این مقدار نادیده گرفته می‌شود و خود وهله بازگشت می‌یابد.

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

function User (name, email) {
this.name = name;// instance variables
this.email = email;

// You can define methods here, but is not recommended
// from performance perspective.
// See the prototype method below
this.save = function () {
// Do whatever you want
this.id = +new Date();
//console.log(${this.name} saved to DB successfully!);
return this.id;
}
}

// PROTOTYPE: Recommended way to create instance methods
User.prototype.saveDB = function () {
this.id = +new Date();
//console.log(${this.name} saved to DB successfully!);
return this.id;
}

بنابراین تابع سازنده فوق قالبی برای یک شیء User نمایش می‌دهد. این تابع نام و ایمیل را به عنوان آرگومان می‌گیرد و آن را به وهله جدیداً ایجاد شده انتساب می‌دهد. این کار زمانی صورت می‌گیرد که فردی آن را با کلیدواژه new فراخوانی کند. شما همچنین می‌توانید متدهایی را مستقیماً به تابع اضافه کنید؛ اما این کار در مواردی که می‌خواهید اشیای زیادی ایجاد کنید توصیه نمی‌شود. در مورد ایجاد یک وهله یا یک سینگلتون، این امر اشکالی ندارد.

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

let user = new User('rajesh', 'someemail@test.com');
console.log(user);
console.log(user.save());
console.log(user.saveDB());// Exact output as save().

// Access instance variables/methods
console.log(user.email);// Outputs emails

توجه کنید که اگر نمی‌توانید از کلیدواژه this در مورد User استفاده کنید، این تابع آن چنان که انتظار دارید عمل نخواهد کرد، زیرا this در این تابع به یک شیء گلوبال اشاره می‌کند. بنابراین کد زیر کار نمی‌کند.

// WILL NOTE WORK. YOU HAVE TO USE 'new' with Constructor function

let user = User("rajesh","someemail2@test.com");

کد فوق یک وهله از شیء User ایجاد می‌کند و متد ()save را فراخوانی می‌کند. شما درون تابع سازنده می‌توانید از طریق this.variablename به متغیرهای وهله دسترسی داشته باشید. خروجی کنسول در ادامه نمایش یافته است.

بنا بر عرفِ جاوا اسکریپت تابع‌های درون تابع سازنده یا روی پروتوتایپ به نام methods نامیده می‌شوند. دلیل این کار صرفاً جلوگیری از سردرگمی است.

چرا باید از پروتوتایپ برای افزودن متدهای وهله‌ای استفاده کرد؟

اینک می‌دانیم که متد ()save و همچنین ()daveDB در بخش فوق کارکرد مشابهی دارند. پس دلیل استفاده از پروتوتایپ چیست؟

سناریوی زیر را در نظر بگیرید: می‌خواهیم فهرستی از کاربران را از فایل بیرونی یا API بخوانیم و شیءهای user را ایجاد کنیم تا بتوانیم درون اپلیکیشن با آن‌ها کار کنیم. از آنجا که با سرویس‌های API بیرونی در این نوشته سر و کار نداریم؛ خودمان 100 کاربر را در یک حلقه ایجاد کنیم:

let users = [];

for(let i = 1; i <= 100; i++) {

let user = new User(user ${i});

user.email = user${i}@test.com; // Lets create dynamic email

users.push(user);

}

console.log(users);

اینک نگاهی به آرایه users می‌اندازیم:

در آرایه فوق می‌بینید که 100 شیء user و 100 کپی از متد ()save وجود دارند. این وضعیت از نظر حافظه وضعیت بدی است. در حالی که ()saveDB تنها یک بار به پروتوتایپ User الحاق یافته است. و هر متد اضافه شده به پروتوتایپ توسط همه وهله‌های تابع سازنده که در این مورد User است به اشتراک درآمده است.

ایجاد حفاظت در برابر new مفقود هنگام فراخوانی تابع سازنده

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

let user1 = new User("rajesh", "somemail2@test.com");
let user2 = User("rohan", "somemail3@test.com");

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

function User (name, email){
if (!(this instanceof User)) {
return new User(name, email);// Don't forget parameter if any
}

// REST OF THE CODE GOES HERE

}

بنابراین کد فوق مشکل فقدان new در تابع سازنده را رفع می‌کند. ما بررسی می‌کنیم که وهله موجود از نوع User نباشد و سپس یک User جدید را با پارامترهای الزامی ایجاد کرده و بازگشت می‌دهیم. در حالتی که کد فوق به صورت زیر فراخوانی شود:

let user = User("rajesh","someemail@test.com");

به درستی یک وهله از شیء User بازگشت می‌دهد.

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

همان طور که پیش‌تر اشاره کردیم، می‌توانیم هر شیئی را به جز انواع مقدماتی (premetive type) از تابع سازنده بازگشت دهیم. برای نمونه نگاهی به کد زیر بیندازید:

 function Api(baseUrl) {
   let _secret = +new Date();
   let self = this;// in case you access to 'this' of Api.

   return {
     fetchData: function (resource) {
        // Here you cannot use 'this' as 'this' points to the
        // fetchData function.

        // The 'self' variable created above will point the the
        // API instance
        let url = ${baseUrl}/${resource}/;
        console.log(url);
        fetch(${url})
           .then(response => response.json())
           .then(json => console.log(json));
       }
    }
}

رویکرد فوق معمولاً برای الگوی سینگلتون استفاده می‌شود که در آن نیاز دارید با یک وهله سر و کار داشته باشید. همچنین می‌توانید از ساختار شیء برای ایجاد این نوع از رفتار استفاده کنید. در ادامه چگونگی استفاده از تابع فوق را بررسی می‌کنیم. در این قطعه کد از سرویس json رایگان typicode استفاده شده است:

let api = new Api("https://jsonplaceholder.typicode.com");
api.fetchData("posts");// Get posts data

api.fetchData("users");// Get users data

درکی که از کد فوق به دست می‌آید این است که حتی اگر کلیدواژه new ذکر نشده باشد، این تابع به درستی کار می‌کند، زیرا شما به صراحت شیء را بازگشت داده‌اید و به چارچوب this تکیه ندارید. برای نمونه کد فوق را می‌توان به صورت زیر نوشت:

let api = Api("https://jsonplaceholder.typicode.com");
api.fetchData("posts");// Get posts data

سینگلتون

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

var Singleton = (function () {
var instance;

function createInstance() {
var object = new Object("I am the instance");
return object;
}

return {
getInstance: function () {
if (!instance) {
instance = createInstance();
}
return instance;
}
};
})();

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

let instance1 = Singleton.getInstance();
let instance2 = Singleton.getInstance();

console.log(instance1 === instance2);// Will return true

این سینگلتون چطور کار می‌کند؟

ابتدا کل کد را درون یک IIFE قرار می‌دهیم و سپس یک متغیر محلی به نام instance ایجاد می‌کنیم. در متد ()getInstance بررسی می‌کنیم که آیا متغیر محلی قبلاً مقداردهی اولیه شده است یا نه. اگر نشده باشد یک وهله جدید را با استفاده از تابع ()createInstance ایجاد می‌کنیم، در غیر این صورت وهله‌ای که قبلاً ایجاد شده را بازگشت می‌دهیم.

اینک مهم نیست که چند بار ()getInstance را فراخوانی کنید، چون تنها یک کپی از شیء در حافظه بازگشت می‌یابد. این وضعیت الگوی بسیار مفیدی برای ذخیره حافظه و ایجاد تابع‌های کاربردی در مواردی که تنها یک وهله مورد نیاز است، محسوب می‌شود.

کلوژر (بستار)

یک کلوژر (Closure) ترکیبی از یک تابع و محیط واژگانی (lexical) است که تابع در آن اعلان شده است. کلمه lexial اشاره به این واقعیت دارد که حیطه‌بندی واژگانی از مکانی که متغیر درون آن در کد منبع اعلان شده است، برای تعیین این که متغیر موجود است یا نه استفاده می‌کند. تابع‌های تو در تو به متغیرهای اعلان شده در حیطه بیرونی خود دسترسی دارند.

کاربردهای کلوژر

  • کاربرد جزئی
    • فرایند به‌کارگیری یک تابع روی برخی از آرگومان‌هایش است. تابعی که به صورت جزئی استفاده می‌شود برای کاربرد آتی بازگشت می‌یابد. به بیان دیگر یک تابع است که یک تابع را با چندین پارامتر می‌پذیرد و یک تابع با پارامترهای کمتر بازگشت می‌دهد.
  • دستگیره‌های رویداد (Event Handlers)
  • ای‌جکس (Ajax)

نمونه ساده‌ای از یک کلوژر را در ادامه می‌بینید:

در ادامه بررسی می‌کنیم که تابع‌های فوق به چه حیطه‌هایی دسترسی دارند.

  • ()dialog به پارامترهای خود و متغیرهای گلوبال دسترسی دارد.
  • ()message به پارامترهای خود، m1، تابع‌های والد، پارامتر d1 در ()dialog دسترسی دارد و همچنین به privateVar دسترسی دارد.
  • ()show به پارامترهای خودش، p1، حیطه تابع والد، message و متغیرهای تابع dialog دسترسی دارد.
  • در نهایت باید گفت که وقتی تابع ()show فراخوانی می‌شود به همه پارامترهایی که درون آن قرار دارند دسترسی دارد، گرچه این متغیرها و پارامترها در هنگام اجرای متد show در حیطه آن قرار ندارند.
  • این همان مفهوم کلوژر است. کلوژر همه متغیرهای جانبی و تابع‌ها را مدت‌ها پس از آن که از حیطه‌اش خارج می‌شوند، نگهداری می‌کند.

هشدار: کلوژرها ممکن است در صورت استفاده نامناسب، موجب نشت حافظه بشوند.

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

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

==

telegram
twitter

میثم لطفی

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

بر اساس رای 1 نفر

آیا این مطلب برای شما مفید بود؟

نظر شما چیست؟

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