ক্লোজার - নামত ছুনাহি হগা। জাভাস্ক্রিপটের দুনিয়াতে সব থেকে গুরত্বপূর্ন কঞ্ছেফটের একটি কঞ্ছেফট হল এই ক্লোজার। আর বিগিনারদের কাছে এই কঞ্ছেফটি সব থেকে ভিতিকর, যদিও কঞ্ছেফটা এত সহজ যে, কেউ যদি একবার ধরতে পারে তাহলে সে বলবে, "আরে আমি এত বোকা!"। ক্লোজারকে ভাল ভাবে বুঝতে হলে যে বিষয় গুলো আগে জানা জরুরি -
১। স্কোপ্, ২। লেক্সিকাল স্কোপ্, ৩। এক্সিকিউশান কন্টেক্সট এদের নিয়েও আমি সংক্ষিপ্ত আলোচনা করার চেষ্টা করব ইন্-শা-আল্লাহ।
fahim faisaal
Today's agenda -
🔸 Scope
🔸 Lexical Scope
🔸 Execution Context
🔸 Closure in Action
🔸 The End
Scope
Scope এর অর্থ এলাকা অর্থাৎ এর সমার্থক হিসেবে আমরা বলতে পারি Area. জাভাস্ক্রিপ্টেও এরকম এলাকা আছে যার মাধ্যমে সে বুঝতে পারে যে, কোন ফাংশান, ভারিয়েবল কোথায় গ্রহনযোগ্য আর কোথায় গ্রহনযোগ্য নয়। আর এই Scope এর মাধ্যমে ফাংশান তার Isolation ধর্মটা অবলম্বন করতে পারে অর্থাৎ একটা ফাংশানের ভিতরে কি ঘটতেছে তা অন্য কোনো ফাংশান বা স্কোপ্ কখনই জানতে পারবে না।
Kind of scope
জাভাস্ক্রিপ্টে প্রধানত স্কোপ্ আছে দুই ধরনের →
- Global Scope
- Local Scope
Global Scope → সহজ কথায় বলতে প্রত্যেকটি জাভাস্ক্রিপ্ট ফাইল হলো একটি করে গ্লোবাল স্কোপ্ অর্থাৎ যদি কোন ভারিয়েবল বা ফাংশান কোনো ফাংশানের ভেতরে ডিকলার করা না হয় তাহলে সেইটাই গ্লোবাল স্কোপের ভিতরে থাকবে। আর এদেরকেই গ্লোবাল ভারিয়েবল বা গ্লোবাল ফাংশান বলা হয় কারন এদেরকে যে কোনো ফাংশান বা যে কোনো স্কোপের ভেতর অ্যাকসেস করা যায়। যেমন →
// Global Scope
var globalVar = "I am visible from everywhere";
function globalFunc() {
console.log("I am visible from everywhere")
}
function test() {
console.log(globalVar);
globalFunc();
}
console.log(globalVar); // output - I am visible from everywhere
globalFunc() // output - I am visible from everywhere
test();
// outout -
// I am visible from everywhere
// I am visible from everywhere
কোনো ভারিয়েবল গ্লোবাল স্কোপে আছে কি না তা দেখার জন্য →
// Global Scope
var a = 10;
console.log(window.a); // 10
function globalFunc() {
return "Hello"
}
var globalVar = globalFunc();
console.log(window.globalVar); // Hello
ব্রাউজার কনছলে যদি উক্ত কোড দেখা হয় তাইলে আমরা সঠিক আউটপুট পাব। window
একটি ব্রাউজার গ্লোবাল অব্জেক্ট। যেই ভারিয়েবল অথবা ফাংশান গ্লোবাল স্কোপে ডিকলার করা হবে সেইটাই এই window
অব্জেক্ট অর্থাৎ গ্লোবাল অব্জেক্টে যুক্ত হবে। তবে মনে রাখতে হবে যে শুধু let
const
দিয়ে কোন ভারিয়েবল বা ফাংশান এক্সপ্রেসান ডিকলার করলে তা গ্লোবাল অব্জেক্টে যুক্ত হয় না।
Local Scope → সাধারনত কোনো ভারিয়েবেল বা ফাংশান কোনো ফাংশানের ব্লকের ভেতর থাকলে সেইসব ফাংশান এবং ভারিয়েবলকে লোকাল ফাংশান বলা হয়। এর কারন বলা যায় Isolation, জাভাস্ক্রিপ্টে প্রত্যেকটি ফাংশানের ব্লকই এক একটি লোকাল স্কোপ্। কোনো গ্লোবাল এবং পারেন্ট স্কোপ্ কখনই জানবে না যে, একটি লোকাল স্কোপে কি ভারিয়েবল এবং ফাংশান ডিকলার করা আছে। যেমন→
// Global Scope
function globalFunc() {
// Local Scope
var localVar = 10;
function localFunc() {
console.log('I am imvissible from global scope');
}
}
globalFunc();
console.log(localVar); // output - ReferenceError: localVar is not defined
localFunc(); // output - ReferenceError: localFunc is not defined
উক্ত কোড থেকে বুঝা যাচ্ছে যে, ফাংশান ব্লকের ভেতর যাই ডিক্লার করা হক না কেন তা বাহিরে থেকে কোনো ভাবেই অ্যাক্সেসযোগ্য না বরং জাভাস্ক্রিপ্ট রানটাইম আমাদের এরর দেবে, যদিও ঐ ফাংশানকে কল করা হয়। সহজ কথায় বললে লোকাল স্কোপে যাই থাকুক না কেন গ্লোবাল স্কোপ্ তা কোনভাবেই জানতে পারবে না।
Lexical Scope
সহজ কথায় বলা যায় যে, লেক্সিকাল স্কোপ্ মানে পারেন্টের স্কোপ্। এখন প্রশ্ন হল পারেন্টের স্কোপ্ আবার কি? তো এখন প্রথমে পারেন্ট স্কোপ্ এবং চাইল্ড স্কোপের ব্যপারে জানা যাক →
//* Global Scope & Parent Scope for all local Scope
function Parent() {
//* Local Scope & parent scope of localFunc
console.log('I am child of Global scope & Parent of localFunc')
function Child() {
//* local Scope & child scope of globalFunc
console.log('I am child of globalFunc')
}
Child();
}
Parent()
এর থেকে দুইটা জিনিস আমরা বুঝতে পারি আর তা হল →
- গ্লোবাল স্কোপ্ সব সময়ের জন্য সকল লোকাল স্কোপের পারেন্ট স্কোপ্ অর্থাৎ গ্লোবাল স্কোপ কখনো চাইল্ড স্কোপ হতে পারে না।
- লোকাল স্কোপ্ কোন লোকাল স্কোপের পারেন্ট হতে পারে আবার নাও পারে, তবে একটি লোকাল স্কোপ্ কোন পারেন্ট স্কোপের চাইল্ড স্কোপ্ অবশ্যই হবে। আর জাভাস্ক্রিপ্টে লোকাল স্কোপ্ মানেই ফাংশান স্কোপ্ অর্থাৎ ফাংশান ছাড়া কোনো লোকাল স্কোপ হয় না।
ফাংশানের দিক্ থেকে বললে, কোনো ফাংশানের ভেতরে আরেকটি ফাংশান ডিক্লার করলে, যেই ফাংশানের ভেতরে নতুন ফাংশানটি ডিক্লার করা হল সেটি পারেন্ট ফাংনশান, আর ঐ পারেন্ট ফাংশানের ভিতরে যেই ফাংশান ডিক্লার করা হবে সেই সকল ফাংশান ঐ পারেন্ট ফাংশানের চাইল্ড ফাংশান।
function Parent() {
var parentVar = "I am parents variable"
function Child() {
console.log(parentVar);
}
Child();
}
Parent(); // I am parents variable
উপরের কোড থেকে আমরা দেখতে পাই যে, Parent
ফাংশানের চাইল্ড ফাংশান হলো Child. ParentVar
ভারিয়েবল Child
ফাংশানের পারেন্ট স্কোপ্ বা ফাংশানে ডিক্লার করা। এরপরও Child
ফাংশান সেই ভারিয়েবলকে আকসেস্ করতে পারছে। Child
ফাংশান তার পারেন্টের স্কোপ্ বা ফাংশান থেকে যে সম্পর্কের মাধ্যমে ভারিয়েবল আকসেস্ করতেছে, এই পারেন্ট এবং চাইল্ড ফাংশানের মধ্যে সম্পর্কই হলো Lexical Scope.
সূত্রঃ যেকোন চাইল্ড ফাংশান তার পারেন্টের সকল কিছু অর্থাৎ ভারিয়েবল , ফাংশান আকসেস্ করতে পারবে তবে কোনো পারেন্ট ফাংশান চাইল্ড ফাংশানের কোনো কিছুই আকসেস্ করতে পারবে না।
function Parent() {
function Child() {
var childVar = "I am childs variable"
}
console.log(childVar);
Child();
}
Parent(); // output - ReferenceError: childVar is not defined
এথেকে বুঝা যায় লেক্সিকাল স্কোপ্ তখনই হয় যখন কোনো পারেন্টর স্কোপ্ এর ভারিয়েবল বা ফাংশান, সেই পারেন্ট ফাংশানের কোনো চাইল্ড ফাংশান থেকে আকসেস্ করা হয়।
পারেন্ট স্কোপের সব ভারিয়েবল এবং ফাংশান চাইল্ড স্কোপে আকসেস্ করা সম্ভব তা আমরা জানি।
এর পাশাপাশি সেই ভারিয়েবল এবং ফাংশানকে পরিবর্তনও করা সম্ভব তবে এতে পারেন্ট স্কোপের ভারিয়েবলেরও পরিবর্তন ঘটে।
function parentFunc() {
// Parent Scope of childFunc
var a = 20;
function childFunc() {
// Local & Child Scope
a = 10;
console.log("a =", a, "form Func"); // a = 10 from Func
}
childFunc();
console.log("I have changed!", a); // I have changed! 10
}
parentFunc();
কারন এখানে পারেন্ট ভারিয়েবলকেই পরিবর্তন করা হচ্ছে, কোন নতুন ভারিয়েবল তৈরী হচ্ছে না।
আমরা জানি, যেখানেই আমরা var
কিওয়ার্ড দিয়ে কোনো ভারিয়েবল ডিক্লার করি সেখানেই একটি নতুন ভারিয়েবল তৈরি হয়। এখন যদি উক্ত কোডের লোকাল এবং চাইল্ড স্কোপে a = 10
এর সামনে যদি var
কিওয়ার্ডটা ব্যবহার করি তাহলে কি হয় দেখা যাক →
// Global & Parent Scope
var a = 20;
function Func() {
// Local & Child Scope
var a = 10;
console.log("a =", a, "form Func"); // a = 10 from Func
console.log("I am from global", window.a); // I am from global 20
}
Func();
console.log(a); // 20
আউটপুটে কোন পরিবর্তন নেই, এর কারন চাইল্ড স্কোপের ভেতরের a এবং পারেন্ট স্কোপের a একনয়। আর আমরা জানি যে, পারেন্ট স্কোপ্ কখনই জানবে না যে, চাইল্ড স্কোপে কি ঘটতেছে।
এই কারনে এখানে সঠিক আউটপুট দিচ্ছে কোনো রকম পরিবর্তন ছাড়া।
অর্থাৎ এখানে কোনো লেক্সিকাল স্কোপিং ঘটছে না।
Execution Context
Closure একটি স্কোপ এবং অব্জেক্ট যেখানে ভারিয়েবল ও ফাংশান স্টোর হয়। এখন প্রশ্ন হলো এই স্কোপ্টি কখন তৈরী হয়?
এটাও জাভাস্ক্রিপ্টের ডিফল্ট একটি কঞ্ছেফট যার জন্য আমাদের কোনো কোড লিখতে হবে না।
জাভাস্ক্রিপ্ট ইঞ্জিন যেভাবে জাভাস্ক্রিপ্ট কোডকে এক্সিকিউট করে মেশিন কোডে রুপান্তুর করে সেই প্রোসেসকে Execution Context বলা হয়। জাভাস্ক্রিপ্ট ইঞ্জিন কয়েক ধরনের আছে, যেমন →
- V8 - Chrome
- Spider Monkey - Mozila Firefox
- Chakra - Internet Explorer
- JavaScript Core - Safari
NodeJs এ V8 ইঞ্জিন ব্যবহার করা হয় কারন এটা বেশি অপ্টিপাইজ ভাবে এক্সিকিউট করে। ইঞ্জিন যাই হোক না কেন তার কাজ একটিই আর তা হল জাভাস্ক্রিপ্ট কোডকে মেশিন কোড অর্থাৎ 1 & 0 তে রুপান্তর করা। সকল ইঞ্জিন ভিন্ন ভিন্ন ভাবে ইমপ্লিমেন্ট করা হলেও সব ইঞ্জিন EcmaScript স্পেসিফিকেশন মেনে চলে।
JIT Compiler
জাভাস্ক্রিপ্টের সকল ইঞ্জিন JIT - Compiler - ( Just-In-Time Compiler ) হিসেবে জাভাস্ক্রিপ্ট কোডকে কম্পাইল করে। এই কম্পাইলেশনে যখন কোনো ফাংশান কল করা হয় তখন সেই ফাংশান বডির ভিতরে যা আছে সব কিছু কে সাথে সাথে মেশিন কোডে রুপান্তর করে এবং এক্সিকিউট করে যদি কোন এরর না থাকে। আর এরর থাকলে তখন JIT - Compiler সেই খানে কম্পাইলেশন বন্ধ করে দিবে তবে যেই লাইনে এরর হবে সেই লাইনেই গিয়ে কম্পাইলেশন বন্ধ করবে এর আগের লাইনে যদি কোন এরর না থাকে তাহলে কম্পাইল এবং এক্সিকিউট করবে। যেমন →
function executable() {
console.log('I am executable')
}
executable();
console.log(IAmError);
// Output
// I am executable
// ReferenceError: IAmError is not defined
প্রথমেই যখন কোন জাভাস্ক্রিপ্ট ফাইল এক্সিকিউট হয় তখন Global Execution Context তৈরী হয়। এখন প্রশ্ন হলো, এইটা আবার কি? 😐
এইটা তেমন কিছুই না, এর মাধ্যমে প্রত্যেক জাভাস্ক্রিপ্ট ফাইল এক্সিকিউট হয়, আর এই এক্সিকিউশান কন্টেক্সটে সর্বপ্রথম একটি অব্জেক্ট তৈরি হয়। যা ব্রউজার কনছলে window এবং NodeJs এ global অব্জেক্ট।
Browser Console
Node Js on terminal
আমি আলোচনা করব ব্রাউজার কন্ছল নিয়ে। দুইটা একি ভাবে কাজ করে শুধু NodeJs এ DOM - ( Document Object Model ) থাকে না আর কিছু কিছু পার্থক্য আছে।
Global Execution Context টা দুই ধাপে সম্পূর্ন হয় →
- Creation Phase
- Execution Phase
Creation Phase - এই ফেজে মূলত চারটি কাজ হয় আর তা হল →
window
- Global Objectthis
-window
variable object
scope chain
এই ফেজে মুল কাজ এই চারটি। variable object এর মাধ্যমে ভারিয়েবল এবং ফাংশানের রেফারেঞ্ছ গ্লোবাল অব্জেক্টে রাখে।
তবে এই ফেজে জাভাস্ক্রিপ্ট ইঞ্জিন var
দিয়ে ডিক্লার করা ভারিয়েবলের ভালু undefined বরাদ্দ করে রাখে। যেমন →
আমরা যদি লিখি var a = 10;
তাহলে এই ফেজে ভারিয়েবলের মান হয় a = undefined
console.log(a); // undefined
var a = 10;
তবে এখানে let const দিয়ে ডিক্লার করলে Reference Error দিবে।
এই জন্য যখন আমরা কোনো ভারিয়েবল ডিক্লার করার এক লাইন আগে সেই ভারিয়েবলকে এক্সিকিউট বা কঞ্ছল লগ করি তখন সেই ভারিইয়েবলের ভালু আমরা দেখি undefined
আর ফাংশানের রেফারেঞ্ছটা যে গ্লোবাল অব্জেক্টে স্টোর হয় তার প্রমান যদি দেখতে চাই তাহলে আমাদের লাগবে ব্রাউজার কঞ্ছল।
function reference
এর থেকে বুঝা গেল আমরা যেই ফাংশান function কিওয়ার্ড দিয়ে ডিফাইন করব সেই ফাংশানের রেফারেঞ্ছ গ্লোবাল অব্জেক্টে যুক্ত হবে।
Execution Phase - এই ফেজে মূলত দুইটা কাজ হয় আর তা হল →
Value Assign
Function Execute
var a = undefined; // In Creation phase
a = 10; // In Execution phase
এই ফেজে এসে কম্পাইলার আবার প্রথম থেকে এক এক লাইন করে রিড করবে এবং creation phase এ ডিক্লারকৃত ভারিয়েবল এর মান undefined
তা আমরা জানি a = undefined
। তবে এই ফেজে এসে কম্পাইলার 10 ভালুকে a ভারিয়েবলে বরাদ্দ করবে। অর্থাৎ এই ফেজে a = 10
হবে।
এই জন্য আমরা যখন ভারিয়েবল ডিক্লার করার পর সেই ভারিয়েবল কে আকসেস্ করার চেষ্টা করি তখন কম্পাইলার আমাদের সঠিক ভালুটা আউটপুট দেয়।
var a = 10;
console.log(a); // 10
কারন এক্সিকিউশান কন্টেক্সটে এসে সেই ভারিয়েবলের ভালুটা ভারিয়েবলে বরাদ্দ হয়ে যায় আর আমরা সঠিক ভালুটা দেখতে পাই।
আর যদি কোথাও কোনো তৈরী করা ফাংশানকে কল করা হয় তখন এই ফেজে এসে সেই ফাংশানটি এক্সিকিউট হয় কারন creation phase এ গ্লোবাল অব্জেক্টে অলরেডি সেই ফাংশানের রেফারেঞ্ছ আছে যা আমরা উপরের উদাহরণ থেকে দেখে আসছি। এই কারনেই function কিওয়ার্ড দিয়ে তৈরী করা ফাংশান আমরা যেখান থেকেই কল করি না কেন তা আমাদের সঠিক অউটপুট দেয়, ভারিয়েবলের মত undefined
এবং Reference Error দেয় না।
myFunc(); // I am executable from everywhere
function myFunc() {
console.log("I am executable from everywhere");
}
myFunc(); // I am executable from everywhere
Creation phase এ কিভাবে ভারিয়েবলের ভালু undefined
থাকবে আবার তা Execution Phase এ এসে সেই ভারিয়েবলের ভালু বরাদ্দ হবে, একই ভাবে কিভাবে ফাংশানের রেফারেঞ্ছ স্টোর হবে গ্লোবাল অব্জেক্টে আবার তা Execution phase এ এসে কিভাবে এক্সিকিউট হবে এই কঞ্ছেফটকে মূলত Hoisting বলে।
আরেকটি গুরুত্বপূর্ন বিষয় হচ্ছে যেখানেই একটি লোকাল স্কোপ অর্থাৎ ফাংশান বডি শুরু হয় সেখানেই আরেকটি নতুন এক্সিকিউশন কন্টেক্সট শুরু হয় অর্থাৎ সেখানেও Creation Phase এবং Execution Phase এঁর সূচনা হয়।
তবে শুধু পার্থক্য হল একটাই, লোকাল এক্সিকিউশান কন্টেক্সটের Creation Phase এ কোনো global বা window অব্জেক্ট তৈরী হয় না কারন তা গ্লোবাল এক্সিকিউশান কন্টেক্সটেই তৈরী হয়ে যায়। এর বদলে সে একটি parameter / arguments নামে একটা অব্জেক্ট তৈরী করে যেখানে ফাংশানের পারামিটার গুলো স্টোর হয়ে থাকে।
এক্সিকিউশান কন্টেক্সটে আরেকটি মজার বিষয় হল, ফাংশান যখন কল হয় তখন ঐ ফাংশানটা Call Stack নামের একটা কন্টেইনারে যায় আর এক্সিকিউশান হওয়ার পর সেখান থেকে সেই ফাংশানটা রিমুভ হয়ে যায়।
এই Call Stack সকল ফাংশান এক্সিকিউশান কে Stack - Data Stracture এ রাখে যার কঞ্ছেফট আমরা অনেকেই জানি - LIFO ( Last In Fast Out ) অর্থাৎ যে ফাংশানটি লাস্টে কল বা স্টাকে ঢুকবে সেই সবার আগে এক্সিকিউট হবে।
function Third() {
function Second() {
function First() {
console.log("I am First")
}
First()
console.log("I am second")
}
Second()
console.log("I am third")
}
Third()
// I am First
// I am second
// I am third
একটি ফাংশানের ভেতর যদি আরেকটি ফাংশান কল হয় তাহলে সেই মূল ফাংশানের এক্সিকিউশান বন্ধ হয়ে যাবে আর কম্পাইলার তখন কলকৃত ফাংশানে লোকাল এক্সিকিউশান কন্টেক্সট শুরু করবে অর্থাৎ Call Stack এ ঐ ফাংশানটা যুক্ত হবে। দ্বিতীয় ফাংশানটা এক্সিকিউট হওয়ার পর Call Stack থেকে ঐ ফাংশান বের হয়ে যাবে তারপর কম্পাইলার আবার প্রথম ফাংশানে যাবে এবং সেই ফাংশানকে এক্সিকিউট করে Call Stack থেকে বের করে দিবে।
Closure in Action
ক্লোজার বুঝার মুল সুত্র একটা আর তা হল, যেখানে ক্লোজার হবে সেখানে অবশ্যই লেক্সিকাল স্কোপিং ঘটবে অর্থাৎ লেক্সিকাল স্কোপিং না হলে সেই খানে ক্লোজার হওয়ার কোন সুযোগ নেই।
তাহলে এমন একটি উদাহরণ দেখা যাক যেখানে লেক্সিকাল স্কোপিং হচ্ছে →
function Parent() {
var a = 10;
function Child() {
return a + 20;
}
return Child
}
var closure = Parent()
এই কোডে লেক্সিকাল স্কোপিং হচ্ছে তা নিশ্চিত, কারন a ভারিয়েবল Parent() ফাংশান স্কোপে ডিক্লার করা হয়েছে কিন্তু তা আক্সেস করা হয়েছে Child() থেকে।
আমরা এক্সিকিউশান কন্টেক্সট থেকে জানলাম যে, প্রত্যেক ফাংশান এক্সিকিউট হওয়ার পর সেই ফাংশান কিছু রেটার্ন করে Call Stack থেকে রিমুভ হয়ে যায়, যদি আমরা কিছু রিটার্নও না করি তাহলে জাভাস্ক্রিপ্টের সব ফাংশান ডিফল্ট ভাবে
আমরা এক্সিকিউশান কন্টেক্সট থেকে জানলাম যে, প্রত্যেক ফাংশান এক্সিকিউট হওয়ার পর সেই ফাংশান কিছু রেটার্ন করে Call Stack থেকে রিমুভ হয়ে যায়, যদি আমরা কিছু রিটার্নও না করি তাহলে জাভাস্ক্রিপ্টের সব ফাংশান ডিফল্ট ভাবে undefined রিটার্ন করে আর সেই ফাংশানের কোনো ভারিয়েবল মেমুরিতে স্টোরও থাকে না।
এখন আমরা উক্ত কোডের Parent() এর লোকাল এক্সিকিউশান কন্টেক্সক্ট খিয়াল করি -
- Creation phase
- parameter / arguments object { } : Empty object cause there have no arguments / parameter
a = undefined
- Execution Phase
a = 10
return Child
এখানে খেয়াল করলে আমরা দেখব যে, Child ফাংশানটা কল করা হয়নি শুধু Child ফাংশানের রেফারেঞ্ছটা রিটার্ন করা হইছে। ফাংশান কল করা হয় নাই মানে লোকাল এক্সিকিউশান কন্টেক্সটই হয় নাই এক্সিকিউটতো দূরের কথা।
যেহেতু Parent() ফাংশান Child ফাংশানের রেফারেঞ্ছটা রিটার্ন করলো তাহলে আমরা তো শুধু child ফাংশান্টাই পাচ্ছি। তার প্রমান আমরা বাউজারে দেখলেই পাব →
Child function reference
এখন যদি আমরা child ফাংশানকে এক্সিকিউট করতে চাই তাহলে আমাদের closure() কে কল করতে হবে।
আচ্ছা, করি তাইলে কল, দেখি কি আসে →
call child function
এই আমাদের সঠিক আউটপুট দিচ্ছে। কিন্তু কেন? কারন এক্সিকিউশান কন্টেক্সট থেকে আমরা জানি যে, ফাংশানের সব কাজ শেষ হয়ে গেলে সেই ফাংশান Call Stack থেকে বের হয়ে যায় এবং সব মেমরিও ক্লিন হয়ে যায়।
তাহলে এখন প্রশ্ন হচ্ছে, এই Child ফাংশানে a এর ভালু 10 পাচ্ছে কিভাবে? কারন Parent ফাংশানের সব কিছু তো এক্সিকিউশান কন্টেক্সট হিসেবে রিমুভ হয়ে গেছে অর্থাৎ a বলতে কিছুই নেই Parent ফাংশানে। তাহলে এখন কিভাবে Child ফাংশান লেক্সিকাল স্কোপিং ঘটাচ্ছে?!
আসলেই কি এখনে লেক্সিকাল স্কোপিং ঘটতেছে?
উত্তর হচ্ছে না, এখানেই হচ্ছে ক্লোজার। আমি প্রথমেই বলেছিলাম Closure একটি অব্জেক্ট যেখানে ভারিয়েবল এবং ফাংশান স্টোর থাকে। এখন আমরা যদি console.dir(closure)
দিয়ে দেখি তাহলেই এই ক্লোজার স্কোপ্ বা অব্জেক্টরে দেখতে পাব যেখানে, {a: 10}
স্টোর আছে।
Closure 😍😍
এখানে আমরা দেখতে পাচ্ছি যে [[Scopes]]: Scopes[2]
এর ভেতরে দুইটি আইটেম আছে →
0: Closure
1: Global
global অব্জেক্টা যে Global Execution Context থেকে তৈরী হইছে তা আমরা জানি। এখন প্রশ্ন হল ক্লোজার স্কোপ্টা কেমনে আসল?
যখন জাভাস্ক্রিপ্ট ইঞ্জিন দেখল যে, Parent ফাংশানটা Child ফাংশানের রেফারেঞ্ছকে রিটার্ন করতেছে আর সেখানে লেক্সিকাল স্কোপিংও ঘটতেছে তাহলে যখন Child ফাংশান এক্সিকিউট হবে তখন সে a এর ভালু কই পাবে? তখনতো Parent ফাংশানের কিছুই থাকবে না a = 10
দূরের কথা।
তখন জাভাস্ক্রিপ্ট ইঞ্জিন এই a এর ভালুটাকে Child ফাংশানের স্কোপের সাথে বেধে দিল, যেন যখন Child ফাংশান্টা এক্সিকিউট হবে তখন যেন এই a এর ভালু 10 পায়।
আর যে স্কোপের ভেতরে a = 10
স্টোর হল সেটিই Closure
তবে যদি Parent ফাংশানের ভিতরে Child ফাংশানের রেফারেঞ্ছ রিটার্ন না করে কল করা হত তাহলে কোন Closure হত না কারন কল হওয়া মাত্র local execution context শুরু হত আর আমদের 30 রিটার্ন করত।
The End
ক্লোজার মুলত ফাংশনাল প্রোগ্রামিং এর সব থেকে বড় কঞ্ছেফট কারন ক্লোজার ছাড়া ফাংশনাল প্রোগ্রামিং অসম্পূর্নই বলা চলে। তাই আপনি যদি ক্লোজার একবার বুঝতে পারেন তাইলে যেকোন ফাংশনাল প্রোগ্রামিং লাঙ্গুয়েজ যেমন - Haskell, Scala, Clojure, Erlang ইত্যাদি সহজে বুঝতে পারবেন ইন্-শা-আল্লহ।