नमस्कार मंडळी!

तुम्ही JavaScript मध्ये कोडिंग करत असाल, तर कधी ना कधी Lexical Environment, Scope Chain आणि Closure हे शब्द ऐकलेच असतील. सुरुवातीला हे थोडे किचकट वाटू शकतात, पण एकदा का यांची मूलभूत संकल्पना स्पष्ट झाली की JavaScript ची कार्यपद्धती अधिक चांगल्या प्रकारे समजते आणि तुम्ही अधिक प्रभावी कोड लिहू शकता.

आज आपण या तिन्ही महत्त्वाच्या संकल्पना अगदी सोप्या भाषेत, उदाहरणांच्या मदतीने समजून घेणार आहोत.

कोड म्हणजे एक इमारत, function म्हणजे एक खोली!

आपल्या सोयीसाठी कल्पना करूया की आपला JavaScript कोड म्हणजे एक मोठी इमारत आहे आणि त्या इमारतीतील प्रत्येक function म्हणजे एक वेगळी खोली आहे.

१. Lexical Environment म्हणजे काय? (तुमची 'खोली' आणि तिचा 'नकाशा')

"Lexical" या शब्दाचा सरळ अर्थ आहे कोड लिहिण्याच्या वेळेस गोष्टींची जागा निश्चित असणे.

जेव्हा तुम्ही JavaScript मध्ये एखादा function (म्हणजे एक 'खोली') तयार करता, तेव्हा त्याला आपोआप दोन गोष्टी मिळतात:

  • त्या function च्या आतले सामान: तुम्ही त्या function मध्ये जे variables, constants, किंवा दुसरे functions तयार केले आहेत, ते त्या खोलीतील सामान आहे.
  • बाहेरच्या जगाची माहिती (Parent Scope): तुमची खोली इमारतीच्या कोणत्या भागात आहे आणि तिच्या थेट बाहेर किंवा वरच्या मजल्यावर कोणत्या खोल्या आहेत, याचा एक प्रकारचा 'नकाशा' किंवा संदर्भ त्या function ला मिळतो.

या दोन्ही गोष्टी मिळून त्या function चं Lexical Environment तयार होतं. हे function तयार होतानाच निश्चित होतं.

उदाहरणाद्वारे समजून घेऊ:

function outer() { // ही बाहेरची मोठी खोली
    let a = 10; // या खोलीतील सामान (variable)

    function inner() { // ही outer खोलीच्या आतली एक लहान खोली
        console.log(a); // inner ला a हवा आहे
    }

    return inner; // inner खोलीचा reference बाहेर पाठवला
}

इथे inner() नावाचा function हा outer() नावाच्या function च्या आत लिहिला आहे. inner() function चं Lexical Environment म्हणजे outer() चा scope आणि त्यातील a नावाचा variable यांसारखी माहिती. inner ला माहित आहे की तो outer नावाच्या खोलीत आहे आणि तिथे काय काय उपलब्ध आहे.

थोडक्यात: प्रत्येक function (खोली) जन्माला येताना त्याची स्वतःची वस्तूंची यादी आणि तो कोणत्या मोठ्या खोलीत आहे याचा 'नकाशा' घेऊन येतो. हा नकाशा म्हणजे Lexical Environment.

२. Scope Chain म्हणजे काय? (वस्तू शोधण्याचा 'शोध मार्ग')

आता समजा, तुमच्या function ला (खोलीतील व्यक्तीला) एखादी वस्तू (variable) हवी आहे, पण ती त्याच्या स्वतःच्या खोलीत (त्याच्या local scope मध्ये) उपलब्ध नाहीये. मग तो काय करेल?

  • तो आधी स्वतःच्या खोलीत (local scope) बघेल.
  • जर वस्तू तिथे नसेल, तर तो त्याच्या Lexical Environment च्या 'नकाशा' मध्ये बघेल. नकाशा त्याला दाखवेल की त्याची खोली कोणत्या मोठ्या खोलीच्या (parent scope) आत आहे.
  • मग तो त्या मोठ्या खोलीत जाऊन ती वस्तू शोधायला सुरुवात करेल.
  • जर तिथेही ती वस्तू सापडली नाही, तर तो नकाशा बघून त्याहून वरच्या खोलीत (parent च्या parent च्या scope मध्ये) बघेल आणि असंच 'वरती वरती' शोधत जाईल.

variables शोधण्याच्या या 'वरती वरती जाण्याच्या मार्गाला' किंवा 'साखळीला' Scope Chain म्हणतात. ही स्कोप चेन function च्या Lexical Environment नुसार तयार होते आणि variables चा शोध घेण्यासाठी वापरली जाते.

कोडमध्ये Scope Chain:

function levelOne() { // इमारतीचा पहिला मजला (Outer Scope)
    let gift = "Chocolate"; // levelOne च्या खोलीतील वस्तू

    function levelTwo() { // levelOne च्या आतली खोली (Nested Scope)
        function levelThree() { // levelTwo च्या आतली आणखी लहान खोली (Deeply Nested Scope)
            console.log(gift); // levelThree ला gift हवा आहे
        }
        levelThree();
    }

    levelTwo();
}

इथे levelThree() function ला gift variable ची गरज आहे, पण तो थेट levelThree() च्या आत नाहीये.

  • levelThree() आधी स्वतःच्या scope मध्ये बघेल - नाही सापडला.
  • त्याच्या Scope Chain नुसार, तो त्याच्या parent scope मध्ये, म्हणजे levelTwo() च्या scope मध्ये बघेल - इथेही नाही सापडला.
  • पुन्हा Scope Chain नुसार, तो levelTwo() च्या parent scope मध्ये, म्हणजे levelOne() च्या scope मध्ये बघेल - तिथे gift सापडला! ✅

हा 'शोध मार्ग' (levelThree -> levelTwo -> levelOne) म्हणजे Scope Chain.

३. Closure म्हणजे काय? ('आठवण' सोबत घेऊन जाणं)

आता येते खरी जादू! Closure म्हणजे function ची अशी क्षमता, ज्यामुळे तो त्याच्या जन्म घेत असतानाचं Lexical Environment (त्याचा संदर्भ किंवा 'नकाशा' + 'सामान') स्वतःसोबत घेऊन जातो.

समजा तुमची एक function नावाची 'खोली' आहे. तुम्ही तिला एका मोठ्या खोलीत (parent function मध्ये) तयार केलं, आणि नंतर त्या खोलीला त्या मोठ्या खोलीतून 'बाहेर' काढलं किंवा दुसरीकडे वापरण्यासाठी पाठवलं. जरी ती मोठी खोली ('parent function') आता बंद झाली असली किंवा तिचं काम पूर्ण झालं असलं, तरी लहान function (तुमची 'खोली') त्याच्यासोबत त्याच्या जन्मवेळच्या मोठ्या खोलीचा 'नकाशा' आणि त्यातील variables ची 'आठवण' घेऊन जातो.

या 'आठवणीला सोबत ठेवण्यालाच' Closure म्हणतात.

Closure कसं काम करतं - उदाहरण:

function kitchen() { // स्वयंपाकघर (मोठी खोली/function)
    let sugar = "2 spoons"; // स्वयंपाकघरातील वस्तू (variable)

    function cup() { // स्वयंपाकघरातील एक छोटी कृती (लहान खोली/function)
        console.log(sugar); // cup ला sugar हवा आहे
    }

    return cup; // cup function ला kitchen मधून बाहेर पाठवलं
}

const myCup = kitchen(); // kitchen() function कॉल झाला आणि त्यातून return झालेला cup function myCup या variable मध्ये साठवला.

// आता kitchen() function चं execution पूर्ण झालं आहे आणि ते technically 'बंद' झालं आहे.
// पण, myCup या variable मध्ये अजूनही त्या cup function चा reference आहे.

myCup(); // आता myCup() ला कॉल करा. आउटपुट: 2 spoons

बघा, kitchen() function चं काम पूर्ण झालं आहे. पण जेव्हा आपण myCup() (जो खरं तर तोच cup function आहे) कॉल करतो, तेव्हा तो अजूनही sugar variable ची value दाखवू शकतो! हे कसं शक्य होतं?

कारण जेव्हा cup() function kitchen() च्या आत तयार झाला, तेव्हा त्याने kitchen() चं Lexical Environment (म्हणजे तो kitchen नावाच्या scope मध्ये आहे आणि तिथे sugar नावाचा variable आहे ही माहिती) स्वतःसोबत Closure म्हणून साठवून ठेवली.

जरी kitchen() function चं execution संपलं तरी, cup function (जो myCup मध्ये आहे) त्याच्या क्लोजरमध्ये असलेल्या त्या जुन्या Lexical Environment चा वापर करून Scope Chain नुसार sugar variable शोधतो आणि त्याला तो मिळतो. हीच आहे क्लोजरची ताकद!

आणखी एक उदाहरण:

function bank() { // बँक (मोठी खोली)
    let balance = 5000; // बँकेतील शिल्लक (private variable)

    return function atm() { // बँकेतील ATM मशीन (छोटी खोली / function - हा क्लोजर बनेल)
        console.log("तुमची शिल्लक आहे:", balance); // ATM ला शिल्लक बघायची आहे
    }
}

const userATM = bank(); // बँक सुरू झाली आणि ATM मशीन (function) बाहेर आलं.

// आता बँक बंद झाली आहे (bank() function चं execution संपलं).
// पण...
userATM(); // आउटपुट: तुमची शिल्लक आहे: 5000

इथे atm() function हा bank() function च्या balance variable ला access करू शकतो, जरी bank() function चं execution संपलं आहे. कारण atm() function ने bank() चं Lexical Environment क्लोजर म्हणून पकडून ठेवलं आहे. या पद्धतीने तुम्ही variables ला private ठेवू शकता.

सारांश:

  • Lexical Environment: function कुठे आहे आणि त्याच्या आजूबाजूच्या scope मध्ये काय आहे याची माहिती.
  • Scope Chain: variables शोधण्यासाठी Lexical Environment नुसार तयार होणारा मार्ग.
  • Closure: function ची त्याच्या जन्मवेळच्या Lexical Environment ला लक्षात ठेवण्याची आणि access करण्याची क्षमता.

हे तिन्ही घटक JavaScript मध्ये variables चा access आणि lifetime (किती वेळ ते उपलब्ध राहतील) कसे नियंत्रित करतात हे समजून घेण्यासाठी अत्यंत महत्त्वाचे आहेत.

Closure चा उपयोग Data Encapsulation (माहिती लपवणे), Factory Functions (नवीन functions तयार करणे), Module Pattern आणि Asynchronous Operations मध्ये मोठ्या प्रमाणावर होतो.

आशा आहे की हे स्पष्टीकरण तुम्हाला Lexical Environment, Scope Chain आणि Closure या संकल्पना स्पष्टपणे समजून घ्यायला मदत करेल! आता वेळ आहे थोडा सराव करण्याची आणि तुमच्या कोडमध्ये हे कसे काम करते हे प्रत्यक्ष बघण्याची.

हॅप्पी कोडिंग!