closure در زبان جاوا اسکریپت چه موقع اتفاق میفتد ؟

closure در زبان جاوا اسکریپت چه موقع اتفاق میفتد ؟ نوشته شده در   javascript ۱ مهر , ۱۳۹۷ توسط  عباس حسینی

closure مفهومی است در زبان های برنامه نویسی که به بحث اسکوپ مربوط میشود، بنابراین پیشنهاد میکنم اگر اطلاعات کافی در مورد اسکوپ جاوا اسکریپت ندارید به لینک زیر مراجعه کرده و در مورد آن مطالعه کنید

لینک مطالعه

lexical scope قوانینی را برای زبان جاوا اسکریپت تعیین میکند که بر اساس آن ذخیره داده ها در یک محل و دسترسی به آن در صورت نیاز را فراهم میکند و در پی همین قوانین مفهومی به نام closure به وجود می آید.

لیست مطالب

بررسی closure

برای فهم closure به کد زیر توجه کنید


function A() {
    var x = 2;
    function show() {
        console.log( x );
    }  
    return show;
}    
var B = A();
B(); // 2 

در اینجا صدا زدن تابع A تابع show را به عنوان خروجی برمیگرداند بنابراین عبارت var B = A(); باعث میشود که تابع show  به متغیر B تخصیص داده شود.

حال که تابع A اجرا شد انتظار داریم garbage collector جاوا اسکریپت متغیر های موجود در اسکوپ آن را از حافظه پاک کند یعنی تابع show نباید در زمان اجرا به مقدار x که در اسکوپ A است دسترسی داشته باشد

garbage collector یک امکان برای مدیریت حافظه است که اطلاعاتی که مورد نیاز نیست را از حافظه پاک میکند

اما در اینجا بعد از صدا زدن تابع B مقدار ۲ چاپ میشود آیا این نشان میدهد که garbage collector به وظیفه خود عمل نکرده؟

آیا تابع show هنوز به مقدار x که در اسکوپ تابع A است دسترسی دارد ؟

آیا حتی بعد از اجرای تابع A متغیر های موجود در اسکوپش از حافظه پاک نمیشوند ؟

این ها همه سوالاتی است که در ذهن شما شکل میگیرد و باعث تعجب شما میشود اما نکته در همین است که به دلیل اینکه تابع show  به یکی از متغیر های موجود در اسکوپ تابع A اشاره دارد و قرار است آن را چاپ کند این اسکوپ از بین نمیرود تا هر گاه تابع show صدا زده شد بتواند به آن دسترسی پیدا کند به این متغیر x که تابع show به آن اشاره کرده closure می گویند .

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

موارد استفاده closure

closure این امکان را به ما میدهد از پاک شدن اطلاعات یک اسکوپ جلوگیری کنیم یا داده های global را private کنیم

کد زیر را در نظر بگیرید


var couter = 0;
function add(){
	counter++;
    return counter;
}

کد بالا یک شمارنده است که با هر بار صدا زدن تابع add مقدار counter یک واحد افزایش می یابد.
متغیر counter در اینجا global است . یعنی همه به آن دسترسی دارد و می توانند آن را عمدا یا به اشتباه تغییر دهند.
ما می خواهیم counter را از تیر رس بقیه مخفی کنیم که کسی نتواند آن را تغییر دهد. به اصطلاح می خواهیم counter را closure کنیم .
برای این هدف اول counter باید local باشد تا کسی به آن دسترسی نداشته باشد. پس counter را به داخل یک تابع منتقل می کنیم.


function myCounter(){
    var counter = 0;
 }

دوم اینکه با هر بار صدا زدن myCounter باید یک واحد به counter اضافه شود.


function myCounter(){
	var counter = 0;
    counter++;
}

اما چون در هر بار فراخوانی تابع myCounter مقدار counter با اجرای خط اول تابع صفر می شود ، مقدار نهایی counter همیشه ۱ خواهد بود.
پس باید کاری کنیم که در فراخوانی تابع myCounter فقط خط دوم یعنی counter++ اجرا شود. برای این کار خط دوم تابع را داخل یک تابع دیگر به نام plus قرار می دهیم و در انتهای plus مقدار counter را return می کنیم. سپس تابع plus را return میکنیم. اکنون تابع myCounter را داخل یک متغیر به نام add میریزیم.


function myCounter(){
	var counter = 0;
    function plus(){
        counter++;
        return counter;
    }
    return  plus;
}
var add = myCounter();

حالا add یک تابع است که حاوی دستورات زیر است.
Counter++;
Return counter;

پس ما به جای اینکه تابع myCounter را فراخوانی کنیم تابع add را فراخوانی می کنیم و با هر بار فراخوانی add مقدار counter + 1 برگردانده می شود و مقدار متغیر counter تبدیل به یک closure شده و هر زمانکه تابع add اجرا شود به مقدار آن دسترسی دارد

IFFE چیست

IFFE مخفف کلمه imediately invoked function expression است و علت این نامگذاری این است که function expression در هنگام تعریف بلافاصله صدا زده میشود .

یکی از مهمترین مزایای استفاده از IFFE این است با به وجود آوردن یک اسکوپ ، محتویاتی که داخل آن قرار میگیرد را کپسوله میکند و دیگر به صورت public برای دیگران قابل دسترس نیست

به کد زیر توجه کنید


//Function expression
var add = function(){}
add()

کد زیر یک function expression میباشد ابتدا تعریف شده ولی بلافاصله بعد از تعریف فراخوانی نشده است  حال فرض کنید میخواهیم در حین تعریف ، عملیات صدا زدن هم انجام شود در جاوا اسکریپت پرانتز به ما کمک میکند تا در هنگام تعریف یک function expression بلافاصله بتوانیم آن را صدا بزنیم

به کد زیر دقت کنید


(function(){ /***/ }) ()

در اینجا عبارت function(){ /***/ } از طریق یک پرانتز تعریف شده و توسط پرانتز دوم بلافاصله بعد از تعریف صدا زده شده است و الگوی IFFE را به وجود آورده است

در ادامه روش های دیگر ساخت IFFE را ذکر میکنم که اگر علاقه مند بودید میتوانید از آنها نیز استفاده کنید


!function () { /* ... */ }();
~function () { /* ... */ }();
-function () { /* ... */ }();
+function () { /* ... */ }();