شیرجه عمیق به قلب Closure در جاواسکریپت

مبحث کلوژر توی جاوااسکریپت شاید یکی از مواردی باشه که درکش سخت باشه (شایدم من اینطور حس میکنم🤷🏻‍♂️)

به هرحال آسون یا سخت شما باید قبلش این مواردی رو که لیست میکنم آشنا باشید تا بهتر درکش کنید

Execution Context - Scoping - High Order function

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

اون سومی یعنی High Order function رو الان براتون توضیح میدم بعدش میریم که قلب کلوژر رو بشکافیم😈

خب، ببینیم High Order function چیه ؟؟؟🤔

من دوتا تعریف ناب بهتون میگم که بحثو جمعش کنیم

1️⃣ به تابعی که تابعی را به عنوان آرگومان ورودی دریافت میکنه، میگیم high order function

const sayHello = () => consloe.log( ' salam malikom ' );
button.addEventListener(' click ', sayHello);

الان addEventListener میشه high order function ما و sayHello میشه تابع callback ما

2️⃣ تابعی که یک تابع جدید رو برگردونه(return کنه) رو هم باز یک High order function می نامیم.

const counter = function() {
    let count = 0;

    return function() {
        count++;
    }
}

توی مثال بالا تابع counter میشه high order function ما

ممکنه سوال پیش بیاد برات مگه تابع، یه مقدار یا value هستش که تو به عنوان آرگومان پاسش میدی یا بر میگردونیش از یه تابع ؟؟؟

باید بگم به لطف مفهوم first class function ها بله

مفهوم first class function میگه که جاوااسکریپت با توابع مثل شهروندان درجه یک (first class citizens) برخورد میکنه یعنی توابع رو به شکل یک value یا مقدار در نظر میگیره به همین علته میتونیم به عنوان آرگومان به تابع پاسشون بدیم یا return شون کنیم و در آخرم این رو یادتون نره که توابع هم نوعی object هستن، درواقع همه چیز تو جاوااسکریپت یه آبجکت محسوب میشه حتی آرایه هم نوعی آبجکته



خب دیگه پیشنیاز هارو یاد گرفتید حالا بریم سر وقت بحث اصلیمون یعنی کلوژر

اول ببینیم مشکل از کجا شروع میشه ؟؟؟

☝🏻یه نکته بگم توی بحث یادگیری، اونم اینکه همیشه یه مفهوم رو خواستید درک کنید بفهمید از خودتون بپرسید که چرا اون مفهوم یا ویژگی به وجود اومده؟؟ اصلا اومده چه مشکلی رو حل بکنه ؟؟؟ اینطوری دیگه میدونید کی و کجا ازش استفاده بکنید. اوکی ؟

تابع زیر رو در نظر بگیرید (مثال counter معروفترین مثال) ما میخوایم توی تابع زیر با هربار فراخوانی counter مقدار count رو افزایش بدیم

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

console.log(counter()); 👉🏻 1
console.log(counter()); 👉🏻 1
console.log(counter()); 👉🏻 1

ولی خروجی با هر بار صدا زدن تابع counter میشه 1 و هیچی اضافه نمیشه به نظرت علتش چیه ؟؟؟

دقیقا اینجاست که گفتم باید اون سه مورد پیشنیاز رو بلد باشید، علتش اینه که هربار تابع صدا زده میشه یک Execution context براش ساخته میشه و وارد call stack میشه و با return شدن، تابع از call stack خارج میشه پس این وسط چون count ما توی اسکوپ counter هست با نابود شدن تابع counter ، اونم از بین میره.

پس راه حل چیه ؟؟؟

راه حلش اینه که بیاییم count رو بیرون از اسکوپ تابع یعنی توی Global scope تعریف کنیم تا هر بار تابع وارد call stack میشه و ازش خارج میشه، count ما ازبین نره یعنی اینطوری 👇🏻

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

console.log(counter()); 👉🏻 1
console.log(counter()); 👉🏻 2
console.log(counter()); 👉🏻 3

مشکل حل شد ؟؟ معلومه که نه اومدی چشمشو درست کنی ابروشو کج کردی ، فرض کن من یه تابع دیگه دارم که با count کار میکنه و یه دوتا ازش کم میکنه اون وقت چی ؟؟؟ اینطوری هر تابعی از راه برسه میتونه روی count تاثیر بزاره و عملا مفهوم pure function و side effect رو تحت تاثیر قرار میده (این دوتا رو سرچ بکنید اگه دوست داشتید)



اینجاست که کلوژر به وجود اومد و عین سامورایی ها مشکل رو حل کرد

اجازه بدین به صورت داستان گونه توضیحش بدم تا بهتر براتون جا بیوفته، تصویر زیر رو میون توضیحاتم داشته باش


*️⃣ در ابتدا به ساکن جاوااسکریپت به () const booker = secureBooking میرسه و اسکوپ و Global Execution context رو میسازه

*️⃣ حالا تابع ()secureBooking صدا زده شده و js وارد بدنه اون میشه و Execution Context مربوط به booker رو میسازه و همچنین passengerCount که مقدارش صفر هست رو توی Execution Context ساخته شده میزاره

*️⃣ الان js رسید به return پس تابع ()secureBooking از call stack خارج میشه و تابع داخلی (یعنی همونی که return شده) به متغیر booker اختصاص داده میشه و وارد call stack میشه که empty هم هستش

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

اصلا چطور تابع ()booker به متغیر passengerCount دسترسی داره ؟🤔 مگه تابع ()secureBooking که صاحبش بود از بین نرفت ؟🤔 مگه نباید متغیرشم با خودش از بین میبرد ؟🤔

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

کلوژر یه راز مهم داره که میگه :

☝🏻هر تابع (منظور تابع درونی هست یعنی همونی که return شده) همیشه به متغیر ها (Environment Variable) های Execution Context ساخته شده توسط تابع بیرونی (بیرونی منظورمون تابع ()secureBooking ) دسترسی دارد حتی اگر تابع بیرونی از بین برود (یعنی از call stack خارج شود)

پس توی مثال ما تابع booker همیشه به متغیر های ()secureBooking دسترسی داره حتی با اینکه ()secureBooking از کال استک خارج شده و از بین رفته

پس به این اتصال passengerCount با تابع داخلی میگن کلوژر، اصلا از معنی فارسیش هم که میشه بستار معلومه که یه محیط بسته و ایزوله داره تولید میکنه و دیگه اون دوتا مشکل اول رو نداریم

حالا بریم اون اولین مثال رو با کلوژر پیاده سازی بکنیم :

const counter= function() {
  let count = 0;

return function () {
        count++;
        console.log(count);
    }
}

const x = counter();
x(); 👉🏻 1
x(); 👉🏻 2
x(); 👉🏻 3


درآخر چندتا نکته بگم :

☝🏻 کلوژر یه مفهومه، یعنی چیزی نیست که تو برنامه نویس بخوایی دستکاریش کنی بلکه خود js این کارو میکنه برات

☝🏻اگه console.dir(x) بگیری توی کد بالا میتونی تصویر زیر رو ببینی و این نتیجه رو بگیری که قبل از اسکوپ و گلوبال، تابع برای پیدا کردن متغیر، اول کلوژر رو سرچ میکنه پس کلوژر بر اون دوتا مقدم هستش (نکته کنکوری هست)

بعید بدونم دیگه مشکلی با کلوژر داشته باشی 😌 چون کامل شکافتیمش

امیدوارم بدرتون بخوره و اگه از این دست مطالب دوست داری، توی کانال تلگرامم عضو شو LearnByLearn@