بررسی توابع کلوژر (Closure) در جاوااسکریپت

سلام دوستان. یک موضوع مهم و کاربردی توی جاوا اسکریپت وجود داره و معمولاً توی مصاحبه‌ها هم پرسیده میشه کلوژر یا Closure هست که امروز مفصل با اون آشنا می‌شیم.

کلوژر چیه؟ ?

کلوژر (Closure) چیزی نیست جز یک تابع معمولی که داخل یک تابع دیگه تعریف و return میشه:

function outer() {
  return function inner() {

  }
}

توی این کد تابع inner یک کلوژر هست و به این صورت استفاده میشه:

function outer() {
  return function inner() {
    alert(&quotHi&quot);
  }
}

const x = outer();
x(); //Hi

توی خط ۷ تابع outer رو اجرا کردیم و خروجی اون که تابع inner هست رو ریختیم توی متغیر x. پس الان مقدار متغیر x یک تابع هست و باید مثل یک تابع فراخونی بشه. مثل خط ۸.

اما خب تعریف کردن یک تابع داخل یک تابع دیگه چه مزایایی داره؟ کلوژر دو مشکل رو حل می‌کنه:

۱. پایداری اطلاعات
۲. امنیت اطلاعات


۱. پایداری اطلاعات یعنی چی؟ ?

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

function clicked() {
  let counter = 0;
  counter++;

  return counter;
}

alert(clicked()); // 1
alert(clicked()); // 1
alert(clicked()); // 1

ما توی خط‌های آخر ۳ بار این تابع رو اجرا کردیم و انتظار داریم شمارنده عدد 3 رو به ما نشون بده. اما با اجرای این کد می‌بینیم که شمارنده ۳ بار عدد 1 رو نشون داد. چرا؟

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

اینجا به محض اینکه تابع clicked فراخونی میشه، یک متغیر counter مخصوص همون فراخونی تولید میشه و به محض تموم شدن کار تابع، اون متغیر از بین میره.

توی فراخونی اول (خط ۸) یک counter اختصاصی تولید شد و وقتی کار تابع به پایان رسید از بین رفت. همین قضیه برای فراخونی‌های خط ۹ و ۱۰ هم صدق می‌کنه. این یعنی متغیر counter پایدار نیست.

ادامه رو بخونید. شاید این کار رو بتونیم با ساختن متغیرهای سراسری حل کنیم!


۲. امنیت اطلاعات یعنی چی؟ ?

حالا کد زیر رو در نظر بگیرید. متغیر counter رو بیرون و توی گلوبال اسکوپ تعریف کردیم:

let counter = 0;

function clicked() {
  counter++;
}

clicked();
clicked();
clicked();

alert(counter); // 3

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

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

let counter = 0;

function clicked() {
  counter++;
}

function dblClicked() {
  counter++;
}

clicked();
clicked();
clicked();

dblClicked();
dblClicked();

alert(counter); // 5

تابع dblClicked هم (بدون اینکه از این نکته آگاه باشه که یک تابع دیگه داره از counter استفاده می‌کنه) وابسته به متغیر counter هست. یعنی تغییری که تابع clicked به counter میده، روی خروجی تابع dblClicked هم تأثیر می‌ذاره! ما ۳ بار تابع clicked رو صدا زدیم و انتظار داریم که مقدار counter عدد 3 باشه. اما 5 هست!

متغیر counter به دلیل اینکه تو بیرونی‌ترین قسمت برنامه تعریف شده، از همه جای برنامه قابل دسترسی و تغییر هست و بنابراین نمی‌تونه شامل مقدار قابل اعتمادی باشه. یعنی هر قسمتی از برنامه (مثل دو تابع بالا) هر وقت که دلشون بخواد می‌تونن مقدار این متغیر رو تغییر بدن. این یعنی متغیر counter امن و قابل اتکاء نیست.

پس راه حل چیه؟ چطوری می‌تونیم متغیرهایی داشته باشیم که هم امن هستن و هم پایدار؟ جواب استفاده از کلوژرهاست ?



و اما دوباره به این پرسش میرسیم که کلوژر چیه؟ ?

همونطور که گفتیم کلوژر تابعی هست که توی یک تابع دیگه تعریف و return میشه:

function outer() {
  const msg = &quotHello&quot

  return function inner() {

  }
}

اینجا inner یک تابع داخلی هست که به اون می‌گیم کلوژر. خاصیت توابع داخلی اینه که می‌تونن به اعضای تابع بیرونی دسترسی داشته باشن. اینجا تابع inner به متغیر تابع بیرونی یعنی msg دسترسی داره.

خب این به چه دردی می‌خوره؟

بالاتر گفتیم که وقتی کار یک تابع به پایان می‌رسه، اعضای اون هم از بین میرن. اما این اعضا برای کلوژر همچنان قابل دسترس هست! به بیان ساده‌تر، تابع بیرونی هنگام خداحافظی، میراث خودش رو برای تابع داخلی به جا می‌ذاره و تابع داخلی می‌تونه راهِ تابع بیرونی رو ادامه بده:

function outer() {
  const msg = &quotHello&quot

  return function inner() {
    alert(msg);
  }
}

const show = outer();

show(); // Hello
show(); // Hello

اینجا توی خط ۹ تابع outer اجرا شد و قبل از خداحافظی تابعی رو return کرد. الان مقدار متغیر show همون تابع inner هست. هر بار که show رو مثل یک تابع صدا می‌زنیم، می‌بینیم که Hello برای ما نمایش داده میشه. پس متغیر msg هنوز زنده هست با این نکته که کار تابع outer قبلاً به پایان رسیده!

گفتیم که متغیر msg هنوز زنده هست. اما به دلیل اینکه داخل تابع تعریف شده، از بیرون قابل دسترسی نیست:

function outer() {
  const msg = &quotHello&quot

  return function inner() {
    alert(msg);
  }
}

const show = outer();

show(); // Hello
alert(msg); // Error: msg is not defined

پس متغیر msg هم پایداری داره و هم امنیت ?



بیاین مثال شمارنده رو با کلوژرها بنویسیم:

function makeCounter() {
  let counter = 0;
 
  return function () {
    return ++counter;
  }
}

const clicked = makeCounter();

alert(clicked()); // 1
alert(clicked()); // 2
alert(clicked()); // 3

همونطور که می‌بینیم با هر بار فراخونی تابع clicked یک واحد به counter اضافه شد و در نتیجه 1 و 2 و 3 به ما نمایش داده میشه. متغیر counter فقط برای تابع (متغیر) clicked ساخته شده و همچنین از بیرون قابل تغییر نیست. یعنی فقط و فقط تابع clicked می‌تونه مقدار اون رو تغییر بده.

با هر بار فراخونی تابع makeCounter، یک متغیر counter و یک کلوژر اختصاصی درست میشه. پس با این کد خیلی راحت می‌تونیم یک شمارنده دیگه درست کنیم:

function makeCounter() {
  let counter = 0;
 
  return function () {
    return ++counter;
  }
}

const clicked = makeCounter();
alert(clicked()); // 1
alert(clicked()); // 2

 const dblClicked = makeCounter();
alert(dblClicked()); // 1
alert(dblClicked()); // 2

توی خط ۱۳ یک شمارنده دیگه ساختیم. با اجرای این کد همونطور که می‌بینیم هر دو شمارنده، counter های اختصاصی خودشون رو دارن که هیچ کس جز خودشون نمی‌تونن مقدار اون رو تغییر بدن.


یک مثال دیگه

فرض کنیم می‌خوایم با پیام‌های مختلفی مثل Welcome و یا Hello به کاربرها خوش‌آمد بگیم:

function greeting(type) {
  return function (user) {
    alert(`${type} ${user}! This is our application.`);
  }
}

const welcome = greeting('Welcome');
welcome('Mario'); // Welcome Mario! This is our application.
welcome('Emily'); // Welcome Emily! This is our application.

const hello = greeting('Hello');
hello('Mario'); // Hello Mario! This is our application.
hello('Emily'); // Hello Emily! This is our application.

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