আমি কখনই জাভাস্ক্রিপ্ট এর ক্লোজার বুঝতাম না !

Shegufa Taranjum
Stack Learner
Published in
15 min readMar 24, 2020

--

যেমনটা টাইটেলে বলেছি, জাভাস্ক্রিপ্টের ক্লোজার সবসময়ই আমার কাছে রহস্যময় লাগতো। ক্লোজার বোঝার জন্য অনেক আর্টিকেল পড়েছি, অনেক প্রোজেক্টে ক্লোজার ব্যবহারও করেছি। মাঝে মধ্যে তো প্রোজেক্টের ভেতর ক্লোজার ব্যবহার করেও বুঝতেই পারিনি যে, আমি কাজের ভেতরে ক্লোজার ব্যবহার করছি।

কিছুদিন আগে আমার এক বন্ধুর সাথে কথা বলার সময় আমরা ক্লোজার নিয়ে আলোচনা করি। পরিশেষে তিনি আমাকে ক্লোজার বোঝাতে সক্ষম হন। আজকের আর্টিকেলে আমিও ঠিক একই ভাবে আপনাদেরকে ক্লোজার বোঝাতে চেষ্টা করবো।

শুরু করার পূর্বে

কিছু গুরুত্বপূর্ণ বিষয় থাকে যা ক্লোজার বোঝার পূর্বেই আমাদের জেনে নিতে হবে। তার ভিতরে একটি হচ্ছে Execution Context।

যখন জাভাস্ক্রিপ্টে কোন কোড রান হয় তখন এটি কোন এনভাইরমেন্টে এক্সিকিউট হচ্ছে সেটা খুবই গুরুত্বপূর্ণ এবং সেটা নিচের যে কোন একটি উপায়ে এক্সিকিউট হয়ে থাকে —

গ্লোবাল কোড — এটি ডিফল্ট এনভারমেন্ট যেখানে আপনার কোড প্রথমবারের মতো এক্সিকিউট হবে।

ফাংশন কোড — এক্সিকিউশন ফ্লো টা যখন ফাংশন বডির ভেতর প্রবেশ করবে।

Execution Context টার্মটাকে এনভাইরমেন্ট অথবা স্কোপ হিসেবে ধরে নিন যেখানে আপনার বর্তমান কোডটি এক্সিকিউট হচ্ছে।

অন্যভাবে বলতে গেলে, যখনই আমরা একটা প্রোগ্রাম শুরু করি, তখনই সেটি গ্লোবাল এক্সেকিউশন কন্টেক্সটের ভেতরে থেকে শুরু হয়। আর এই কন্টেক্সটের ভেতর যেসব ভ্যারিয়েবল রাখা হয় সেগুলো গ্লোবাল ভ্যারিয়েবল। কিন্তু, একটি প্রোগ্রামে যখন ফাংশন কল হয় তখন কি ঘটে??

  1. জাভাস্ক্রিপ্ট তখন লোকাল এক্সিকিউশন কন্টেক্সট নামের একটি নতুন এক্সিকিউশন কন্টেক্সট তৈরি করে
  2. এই লোকাল এক্সিকিউশন কন্টেক্সটের ভিতরে এর নিজস্ব কিছু ভ্যারিয়েবল থাকে। এই কন্টেক্সটের ভিতরের ভ্যারিয়েবল গুলোকে লোকাল ভ্যারিয়েবল বলা হয়
  3. এক্সিকিউশন স্ট্যাকে এখন নতুন একটি স্ট্যাক যুক্ত হল। একটি প্রোগ্রাম কতদূর পর্যন্ত এক্সিকিউট হল বা প্রোগ্রামটি কন্টেক্সটের কোন জায়গায় অবস্থান করছে সেসব ট্র্যাক করাই এক্সিকিউশন কন্টেক্সটের কাজ

একটি ফাংশনের শেষ কোথায়? যখন এটি return স্টেটমেন্ট অথবা ক্লোসিং ব্রাকেট এর কাছে পৌঁছায় তখন এর এক্সিকিউশন শেষ হয়ে যায়। যখন একটি ফাংশন শেষ হয় তখন যে বিষয়গুলো ঘটেঃ

  1. এক্সিকিউশন স্ট্যাক থেকে লোকাল এক্সিকিউশন কন্টেক্সট হারিয়ে যায়
  2. ফাংশনটি কলিং কন্টেক্সটে এর রিটার্ন করা ভ্যালু পাঠিয়ে দেয়। যে কন্টেক্সট থেকে একটি ফাংশনকে কল করা হয় সেটিই মূলত সেই ফাংশনের এক্সেকিউশন কন্টেক্সট। এটি হতে পারে কোন লোকাল বা গ্লোবাল এক্সিকিউশন কন্টেক্সট। ফাংশন থেকে রিটার্ন করা ভ্যালু কে কি করতে হবে সেটা সেই ফাংশনের কলিং কন্টেক্সটের উপর নির্ভর করে। ফাংশন থেকে রিটার্ন করা ভ্যালু হতে পারে একটি অবজেক্ট, এ্যারে, একটি ফাংশন, বুলিয়ান বা যে কোন কিছু। যদি কোন ফাংশনের কোন রিটার্ন ভ্যালু না থাকে তাহলে সেটি অটোমেটিক্যালি undefined রিটার্ন করে
  3. লোকাল এক্সিকিউশন কন্টেক্সটের কাজ এখানেই শেষ। লোকাল কন্টেক্সটের ভেতরে যতগুলো ভ্যারিয়েবল ছিল সেগুলোও এখন হারিয়ে গেছে। আর এই জন্যেই এদেরকে লোকাল ভ্যারিয়েবল বলা হয়

একটি সহজ উদাহরণ

ক্লোজারে যাবার আগে, নিচের কোডটির দিকে লক্ষ্য করুন। এই উদাহারণের কোডটি খুবই স্ট্রেট ফরওয়ার্ড। যে কেও এই প্রোগ্রামটি দেখেই বলে দিতে পারবে এর অউটপুট কি হবে।

1: let a = 32: function addTwo(x) {3:   let ret = x + 24:   return ret5: }6: let b = addTwo(a)7: console.log(b)

চলুন দেখা যাক, এই প্রোগ্রামটির জন্য জাভাস্ক্রিপ্ট ইঞ্জিন কিভাবে কাজ করে।

  1. ১ নং লাইনে আমরা ভ্যারিয়েবল a কে গ্লোবাল এক্সিকিউশন কন্টেক্সটের ভেতরে ডিক্লেয়ার করেছি যার ভ্যালু 3
  2. তারপরের বিষয়টা একটু ট্রিকি। ২-৩ নং লাইনে কি ঘটছে? এখানে আমরা গ্লোবাল এক্সিকিউশন কন্টেক্সটে addTwo নামের একটি নতুন ভ্যারিয়েবল ডিক্লেয়ার করেছি। কিন্তু এই ভ্যারিয়েবলে আমরা কি অ্যাসাইন করেছি? এই দুই ব্র্যাকেটের { } এর ভেতরে যা কিছু আছে সবই addTwo ভ্যারিয়েবলের ভ্যালু হিসেবে অ্যাসাইন করা হয়েছে। এই ফাংশন বডির ভেতরের কোন কোডই বর্তমানে এক্সেকিউট হচ্ছে না বা পরিবর্তন হচ্ছে না, শুধুমাত্র পরবর্তীতে ব্যাবহারের জন্য একটি ভ্যারিয়েবলে জমা থাকছে
  3. ৬নং লাইনটি একদমই সিম্পল। এখানে আমরা গ্লোবাল এক্সিকিউশন কন্টেক্সটে b নামের একটি ভ্যারিয়েবল ডিক্লেয়ার করেছি। যখনি কোন ভ্যারিয়েবল ডিক্লেয়ার করা হয় তখন এর ভ্যালু undefined থাকে
  4. ৬নং লাইনে আমরা একটি অ্যাসাইনমেন্ট অপারেটর দেখতে পাচ্ছি। এখন আমরা ভ্যারিয়েবল b তে একটি নতুন ভ্যালু অ্যাসাইন করার অপেক্ষায় আছি। এরপর আমরা দেখতে পাচ্ছি এখানে একটি ফাংশন কল করা হয়েছে। যখনই কোন ভ্যারিয়েবলের পাশে (…) ফার্স্ট ব্র্যাকেট দেখবেন তখনি বুঝতে হবে এখানে একটি ফাংশন কল করা হচ্ছে। আমরা জানি একটি ফাংশন সাধারনত কোন ভ্যালু, ফাংশন, অবজেক্ট বা undefined রিটার্ন করে থাকে। তাই আমাদের কোডে addTwo() ফাংশন যাই রিটার্ন করুক না কেন সেই ভ্যালুটি ভ্যারিয়েবল b তে অ্যাসাইন হবে
  5. কিন্তু তার জন্য আমাদেরকে প্রথমে addTwo() ফাংশনটা কল করতে হবে। যখনই আমরা ফাংশনটা কল করব, তখনই জাভাস্ক্রিপ্ট প্রোগ্রামের গ্লোবাল এক্সিকিউশন কন্টেক্সট মেমোরিতে addTwo নামের ভ্যারিয়েবল খুঁজতে শুরু করবে। এই প্রোগ্রামের ২-৫ নং লাইনের মধ্যেই ভ্যারিয়েবলটি পাওয়া গেছে। এখানে দেখা যাচ্ছে addTwo ভ্যারিয়েবলটির একটি ফাংশন বডি রয়েছে। লক্ষ্য করবেন, এখানে a ভ্যারিয়েবলটি কিন্তু সেই ফাংশনটির আরগুমেন্ট হিসেবে পাঠানো হচ্ছে। তাই এখন জাভাস্ক্রিপ্ট ভ্যারিয়েবল a কে প্রোগ্রামের গ্লোবাল কন্টেক্সটে খুঁজতে শুরু করবে। আর a ভ্যারিয়েবলের ভ্যালু খুঁজে পাওয়ার পর সেই ভ্যালুকে ফাংশনের আরগুমেন্ট হিসেবে পাঠানো হবে। লক্ষ্য করুন, জাভাস্ক্রিপ্ট a ভ্যারিয়েবলের ভ্যালু খুঁজে পেয়েছে এবং a = 3 কে আর্গুমেন্ট হিসেবে পাস করে দিয়েছে। এখন ফাংশনটি এক্সিকিউট হওয়ার জন্য প্রস্তুত
  6. এখন এক্সিকিউশন কন্টেক্সটটি সুইচ করবে এবং একটি নতুন কন্টেক্সট তৈরি হবে যার নাম আমরা দিতে পারি ‘addTwo’ এক্সিকিউশন কন্টেক্সট। এই নতুন কন্টেক্সটটি কল স্ট্যাক এর ওপরে পুশ হবে। লোকাল এক্সিকিউশন কন্টেক্সটে আমরা প্রথমে কি করতে পারি?
  7. আপনি হয়ত ভাবছেন, লোকাল কন্টেক্সটে ret নামের একটি নতুন ভ্যারিয়েবল ডিক্লেয়ার করা হয়েছে। কিন্তু এটি সঠিক উত্তর নয়। সঠিক উত্তর হচ্ছে, আমাদের প্রথমেই ফাংশনের প্যারামিটারের দিকে লক্ষ্য করতে হবে। যেখানে লোকাল কন্টেক্সটে একটি নতুন ভ্যারিয়েবল x ডিক্লেয়ার করা হয়েছে এবং আর্গুমেন্ট আকারে একটা ভ্যালু 3 পাস করা হচ্ছে। এই জন্য x ভ্যারিয়েবলটি 3 দ্বারা অ্যাাসাইন করা হবে
  8. পরবর্তী ধাপে ret নামের একটি নতুন ভ্যারিয়াবল লোকাল কন্টেক্সটে ডিক্লেয়ার করা হয়েছে। যার ভ্যালু undefined
  9. আবারও ৩নং লাইনে চলে আসি, এখানে যোগ অপারেশন ঘটবে। যার জন্য আমাদের x এর ভ্যালু প্রয়োজন। আর্গুমেন্টে পাস করা x এর ভ্যালু এর সাথে 2 যোগ করে ভ্যারিয়েবল ret এ যোগফলটাকে (5) অ্যাসাইন করবে
  10. ৪নং লাইনে আমরা ret ভ্যারিয়েবলের মান রিটার্ন করেছিলাম। তাই এখন সেই ভ্যারিয়েবলের মান হল 5। এই প্রোগ্রামে যখনই ret ভ্যারিয়েবলের মান 5 রিটার্ন করে তখনই ফাংশনটির এক্সিকিউশন শেষ হয়
  11. ৪-৫ নং লাইনে ফাংশনটির এক্সিকিউশন শেষ হয়, তার সাথে সাথে এর লোকাল কন্টেক্সটও হারিয়ে যায়। এবং এর লোকাল ভ্যারিয়েবলগুলোও (x, ret) হারিয়ে যায়। সেক্ষেত্রে কল স্ট্যাক থেকে পুরো লোকাল কন্টেক্সটাই হারিয়ে যাচ্ছে। তবে এই কন্টেক্সট থেকে পাওয়া ret এর ভ্যালু কলিং কন্টেক্সটে থেকে যাচ্ছে। সেক্ষেত্রে বর্তমানের কলিং কন্টেক্সট টাই গ্লোবাল কন্টেক্সটে পরিনত হচ্ছে। কারন, addTwo() ফাংশনটি গ্লোবাল কন্টেক্সট থেকেই কল করা হয়েছে
  12. এখন আমরা সেই ধাপে যাবো যেখানে আমরা আমাদের ret এর ভ্যালু পেয়েছিলাম। সেখান থেকে রিটার্ন করা ভ্যালু আমরা এখন একটি ভ্যারিয়েবল এর মধ্যে অ্যাসাইন করবো। ৬নং লাইনে দেখুন, আমরা ভ্যারিয়েবল b তে ফাংশন থেকে রিটার্ন করা ভ্যালু 5 রেখেছি
  13. এবং ৭নং লাইনে ভ্যারিয়বল b এর ভ্যালুটা কন্সোলে প্রিন্ট করা হয়েছে

একটি সিম্পল প্রোগ্রামের জন্য এটি একটি অনেক লম্বাচওড়া ব্যাখ্যা ছিল। এবং আমরা কিন্তু এখনও ক্লোজার অবধি পৌঁছাতে পারিনি। কথা দিচ্ছি, আমরা সেখানে অবশ্যই পৌঁছাব। তবে তার আগে আরও দুই একটি বিষয় সম্পর্কে পরিষ্কার ধারনা নিয়ে নেব।

লেক্সিকাল স্কোপ

লেক্সিকাল স্কোপ বোঝার জন্য একটি উদাহরন দেখা যাক।

1: let val1 = 2
2: function multiplyThis(n) {
3: let ret = n * val1
4: return ret
5: }
6: let multiplied = multiplyThis(6)
7: console.log('example of scope:', multiplied)

এখানে আইডিয়াটা হলো, আমাদের লোকাল এবং গ্লোবাল উভয় এক্সিকিউশন কন্টেক্সটেই ভ্যারিয়েবল আছে। জাভাস্ক্রিপ্ট একটি ভ্যারিয়েবল কে কিভাবে খুঁজে বের করবে? জাভাস্ক্রিপ্টের ক্ষেত্রে, এটি প্রথমেই প্রোগ্রামের লোকাল কন্টেক্সটে ভ্যারিয়েবলটিকে খুঁজবে, না পেলে এর কলিং কন্টেক্সটে খোঁজা শুরু করবে। এবং সেখানেও যদি খুঁজে পেতে ব্যর্থ হয় তাহলে এর ভ্যালুকে undefined হিসেবে ধরে নেবে।

এই উদাহরণটি দেখলেই বিষয়টি পরিষ্কার হয়ে যাবে। যদি আপনি স্কোপ কিভাবে কাজ করে সেই সম্পর্কে জেনে থাকেন তাহলে, আপনি চাইলে এই অংশটুকু স্কিপ করতে পারেন।

  1. গ্লোবাল এক্সিকিউশন কন্টেক্সটে val1 নামের একটি নতুন ভ্যারিয়েবল রাখা হল যার মান 2
  2. ২-৫ নং লাইনে multiplyThis নামের একটি নতুন ভ্যারিয়েবল তৈরি করে তার ভিতরে একটা ফাংশন রাখা হল
  3. ৬নং লাইনে প্রোগ্রামের গ্লোবাল কন্টেক্সটে multiplied নামের একটি নতুন গ্লোবাল ভ্যারিয়েবল ডিক্লেয়ার করা হল
  4. গ্লোবাল কন্টেক্সটের ম্যামরি থেকে multiplyThis ভ্যারিয়েবল্টাকে নিয়ে তার আর্গুমেন্ট হিসেবে 6 পাস করে ভ্যারিয়েবলটিকে ফাংশন হিসেবে এক্সেকিউট করা হল
  5. একটি নতুন ফাংশন কল = একটি নতুন এক্সিকিউশন কন্টেক্সট। তাই এটি একটি নতুন লোকাল এক্সিকিউশন কন্টেক্সট তৈরি করি
  6. সেই লোকাল কন্টেক্সটে n নামের একটি লোকাল ভ্যারিয়েবল তৈরি করে তার ভ্যালু 6 অ্যাসাইন করা হল
  7. ৩নং লাইনে ret নামের একটি লোকাল ভ্যারিয়েবল ডিক্লেয়ার করা হয়েছে
  8. এখানে n এবং val1 নামের দুইটি ভ্যারিয়েবলের মানকে গুন করতে হবে। n এবং val1 এর মান বের করতে হলে আমাদেরকে আগে লোকাল বা গ্লোবাল কন্টেক্সটে খুঁজতে হবে। লোকাল কন্টেক্সটে n =6, কিন্তু val1 নামের কোন ভ্যারিয়েবল লোকাল কন্টেক্সটে পাওয়া যাচ্ছে না। তাই সেটিকে এই ফাংশনের কলিং কন্টেক্সটে খুঁজতে হবে। অবশেষে এর কলিং কন্টেক্সটে val1 = 2 পাওয়া গেল
  9. ৪নং লাইনে ret নামের একটি ভ্যারিয়েবলের মধ্যে এই (n, val1) দুটি ভ্যারিয়েবলের (6*2=12) গুণফলকে অ্যাসাইন করে রিটার্ন করা হয়েছে
  10. যখনই আমাদের লোকাল কন্টেক্সট এর কাজ শেষ তখনই ret এবং n হারিয়ে যাবে। শুধুমাত্র val1 এর ভ্যালু হারাবে না কারন, এটি কলিং কন্টেক্সট থেকে ডিফাইন করা হয়েছে
  11. আবারও ৬নং লাইনে আসি, এখানে কলিং কন্টেক্সটে multiplied নামের ভ্যারিয়েবলে আমরা multiplayThis() থেকে রিটার্ন করা গুনফলের মানকে রেখে দেই
  12. সর্বশেষে ৭নং লাইনের multiplied ভ্যারিয়েবলের মানকে কনসোলে প্রিন্ট করব

এই উদাহরনে আমাদের মাথায় রাখতে হবে, একটি ফাংশন এর কলিং কন্টেক্সট থেকে যেকোনো ভ্যারিয়েবলের অ্যাক্সেস নিতে পারে। আর এই রুলসের অফিশিয়াল নাম লেক্সিকাল স্কোপ।

একটি ফাংশন যখন আরেকটি ফাংশন রিটার্ন করে

প্রথম উদাহরনণটিতে আমরা দেখেছি addTwo() নামের ফাংশনটি একটি ভ্যারিয়েবল রিটার্ন করছে। যেমনটি প্রথম থেকে বলেছি যে একটি ফাংশন যেকোনো কিছু রিটার্ন করতে পারে। চলুন দেখে নেই কিভাবে একটি ফাংশন থেকে অন্য আরেকটি ফাংশঙ্কে রিটার্ন করা যায়। ক্লোজার বোঝার জন্য এই বিষয়টি খুবই গুরুত্বপূর্ণ। নিচের কোড স্নিপেট থেকে আমরা পুরো বিষয়টা বোঝার চেষ্টা করবো।

1: let val = 7
2: function createAdder() {
3: function addNumbers(a, b) {
4: let ret = a + b
5: return ret
6: }
7: return addNumbers
8: }
9: let adder = createAdder()
10: let sum = adder(val, 8)
11: console.log('example of function returning a function: ', sum)

কোডটি ধাপে ধাপে বোঝার চেষ্টা করিঃ

  1. গ্লোবাল এক্সিকিউশন কন্টেক্সটে val নামের একটি নতুন ভ্যারিয়েবল নেওয়া হল যার মান 7
  2. ২-৮ নং লাইনে createAdder নামের একটি গ্লোবাল ভ্যারিয়েবল রাখা হল যার ভেতরে ভ্যালু হিসেবে একটি ফাংশনকে অ্যাসাইন করা হল। ৩-৭নং লাইনে এই ফাংশন এর ডেফিনেশন রয়েছে। পূর্বের মতোই আমরা কিন্তু ফাংশনটিতে ফোকাস করছি না শুধু এই (creatAdder) ভারিয়েবলের ভেতরে ফাংশনটির ডেফিনেশন ষ্টোর করে রেখেছি
  3. ৯নং লাইনে গ্লোবাল এক্সিকিউশন কন্টেক্সটের ভেতর adder নামের একটি নতুন ভ্যারিয়েবল রাখা হল টেম্পোরারিভাবে যার মান undefined
  4. ৯নং লাইনে আমরা একটি ফার্স্ট ব্রাকেটও () দেখতে পাচ্ছি। সেহুত, এখানে আমাদেরকে ফাংশনটি এক্সেকিউট বা কল করতে হবে। আর সেটি করার জন্য আমাদেরকে আগে গ্লোবাল কন্টেক্সট মেমরির ভেতর থেকে createAdder নামের ভ্যারিয়েবলটি খুঁজে বের করতে হবে। লক্ষ্য করুন ২নং লাইনে কিন্তু আমরা এই ভ্যারিয়েবলটি ক্রিয়েট করে রেখেছিলাম। এবার ফাংশনটিকে কল করা যাক
  5. এখন আমরা ২নং লাইন থেকে ফাংশনটি কে কল করে আনলাম। যেখানে একটি লোকাল এক্সিকিউশন কন্টেক্সট তৈরি হয়ে গেল। আমরা ইচ্ছে করলেই এই নতুন কন্টেক্সটের ভেতরে লোকাল ভ্যারিয়েবল ক্রিয়েট করতে পারি। জাভাস্ক্রিপ্ট ইঞ্জিন এই নতুন কন্টেক্সট টাকে তার কল স্ট্যাকে অ্যাড করে নিল। যেহেতু এই ফাংশনটির ভেতরে কোন আর্গুমেন্ট পাস করতে হবে না তাই আমরা ফাংশন বডিতে চলে যাই
  6. ৩-৬ নং লাইনে আমাদের আরেকটি ফাংশন ডিক্লেয়ার করা হয়েছে।এর লোকাল এক্সিকিউশন কন্টেক্সটে addNumbers নামের একটি নতুন ভ্যারিয়েবল তৈরি করা হল যার ভেতরে একটি ফাংশনের ডেফিনেশন রাখা হল
  7. ৭নং লাইনে থেকে addNumbers ভ্যারিয়েবলটিকে রিটার্ন করা হল। আসলে addNumber ভ্যারিয়েবলের ভেতর কি আছে? এর ভেতর একটি ফাংশনের ডেফিনেশন রয়েছে এবং যখনই addnumber ভ্যারিয়েবলকে রিটার্ন করা হচ্ছে তখন এটি একটি ফাংশন ডেফিনেশন রিটার্ন করছে। অর্থাৎ ৪-৫ নং লাইনের মধ্যে বা {} এই দুই ব্র্যাকেটের মধ্যে যা কিছু আছে সবই ফাংশনের ডেফিনেশন। ফাংশনটি শেষ হবার সাথে সাথেই প্রোগ্রামের কল স্ট্যাক থেকে এই লোকাল কন্টেক্সট টি হারিয়ে গেছে
  8. এখন কিন্তু লোকাল কন্টেক্সটের সাথে সাথে লোকাল ভ্যারিয়েবল addNumbers ও হারিয়ে গেছে। কিন্তু এর ভেতরকার ফাংশন ডেফিনেশন টি কিন্তু adder নামের একটি নতুন ভ্যারিয়েবলে অ্যাসাইন করে রাখা হয়েছে
  9. ১০নং লাইনে দেখুন sum নামের একটি ভ্যারিয়েবল রয়েছে যার ইনিশিয়াল ভ্যালু এখন undefined.
  10. এখন আমাদের কোন ফাংশনটা এক্সিকিউট করা দরকার? যে ফাংশনটি sum ভ্যারিয়েবলে অ্যাসাইন করা হয়েছে সেটি(adder)। এই ফাংশনটি দুইটি আর্গুমেন্ট নিচ্ছে
  11. প্রথম আর্গুমেন্ট হিসেবে a = val (যার মান 7) এবং দ্বিতীয় আর্গুমেন্ট হিসেবে b=8 পাস করা হল
  12. এখন আমাদের ফাংশনটিকে এক্সেকিউট করতে হবে। ফাংশনটির বডির ভেতরে আমাদের একটি নতুন লোকাল এক্সেকিউশন কন্টেক্সট তৈরি হয়েছিল। যার ভেতর a, b নামের দুটি ভ্যারিয়েবল রাখা হয়েছিল। ফাংশনটি এক্সিকিউট করার জন্য আমরা a এবং b এর ভ্যালুও অ্যাসাইন করেছি
  13. ৪নং লাইনে লোকাল কন্টেক্সটের ভেতরে ret নামের একটি নতুন ভ্যারিয়েবল ডিক্লেয়ার করা হয়েছিল। যার ভেতর a,b এর যোগফল রাখা হয়েছিল। ফাংশনটি কল করার পর এখন ret এর বর্তমান ভ্যালু (7+8 = 15)
  14. এখন ret এর ভ্যালুকে ফাংশন থেকে রিটার্ন করা হয়েছে, তার সাথে এর লোকাল এক্সিকিউশন কন্টেক্সটও হারিয়ে গেছে। এর ভেতরকার ভ্যারিয়েবল (a,b,ret) কিছুই নেই, সব কন্টেক্সট থেকে হারিয়ে গেছে
  15. ৯ নং লাইনে শুধুমাত্র এর থেকে রিটার্ন করা যোগফলের মান sum ভারিয়েবলে রয়ে গেছে
  16. এবং ১০নং লাইনে সর্বশেষে আমরা যোগফলের মানটাকে অর্থাৎ sum= 15 কনসোলে প্রিন্ট করলাম

আমরা এতক্ষন অনেকগুল বিষয় নিয়ে আলোচনা করলাম। এখনে আমি এর মধ্যে থেকে কিছু বিষয় পয়েন্ট আউট করছিঃ

  • একটি ফাংশনের ডেফিনেশন ভ্যারিয়েবলের মধ্যে স্টোর করে রাখা যায়।
  • যতবারই একটি ফাংশনকে কল করা হয় ততবারই একটি করে নতুন (টেম্পোরারি) কন্টেক্সট তৈরি হয়ে যায়।
  • একটি ফাংশন যখন শেষ হয় তখনই সেই টেম্পোরারি কন্টেক্সট কল স্ট্যাক থেকে হারিয়ে যায়।
  • যখনই একটি ফাংশন থেকে কোনকিছু রিটার্ন করা হয় অথবা ক্লোজিং ব্র্যাকেট } পর্যন্ত পৌঁছায় তখনই একটি ফাংশনের এক্সিকিউশন শেষ হয়।

ক্লোজার

নিচের কোডটি লক্ষ করুন এবং এই প্রোগ্রামটিতে কি হচ্ছে বোঝার চেষ্টা করুনঃ

1: function createCounter() {
2: let counter = 0
3: const myFunction = function() {
4: counter = counter + 1
5: return counter
6: }
7: return myFunction
8: }
9: const increment = createCounter()
10: const c1 = increment()
11: const c2 = increment()
12: const c3 = increment()
13: console.log('example increment', c1, c2, c3)

এখন যেহেতু আমরা পূর্বের উদাহরণগুলো থেকে অনেক কিছু শিখেছি, তাই এখানে বিষদভাবে আলোচনা না করে এর এক্সিকিউশন কন্টেক্সটগুলো নিয়ে একটু আলোচনা করি।

  1. ১-৮ নং লাইনে আমরা প্রোগ্রামের গ্লোবাল কন্টেক্সটে createCounter নামের একটি নতুন ভ্যারিয়েবল ক্রিয়েট করেছি এবং এটিকে একটি ফাংশন হিসেবে ডিফাইন করেছি
  2. ৯নং লাইনে আমরা প্রোগ্রামের গ্লোবাল কন্টেক্সটে increment নামের একটি নতুন ভ্যারিয়েবল ডিক্লেয়ার করেছি
  3. এখন ৯নং লাইনে — আমাদের createCounter নামক ফাংশনটি কল করতে হবে এবং এই ফাংশন থেকে যা রিটার্ন হবে তা increment ভ্যারিয়েবলে রাখতে হবে
  4. যেহেতু createCounter ফাংশনটি কল করা হয়েছে সেহেতু ১-৮ নং লাইনের ফাংশন এক্সিকিউট হওয়া শুরু করবে এবং তার জন্য এক্সিকিউশন কন্টেক্সটে একটি নতুন লোকাল কন্টেক্সট তৈরি হবে
  5. ২নং লাইনে অর্থাৎ টেম্পোরারি লোকাল কন্টেক্সটে counter নামের একটি ভ্যারিয়েবল ডিক্লেয়ার করা হয়েছে শুরুতে যার মান 0
  6. ৩-৬ নং লাইনে myFunction নামের একটি লোকাল ভ্যারিয়েবল ডিক্লেয়ার করা হয়েছে। এই ভ্যারিয়েবলেও আরেকটি নতুন ফাংশনকে অ্যাসাইন করা হয়েছে। ৪-৫ নং লাইনে সেই ফাংশনের ডেফিনেশন রয়েছে
  7. ৭নং লাইনে যখনই আমরা myFunction নামক ভ্যারিয়েবলটাকে রিটার্ন করছি ঠিক তখনই আমার লোকাল কন্টেক্সটের এক্সিকিউশনের কাজ শেষ। এখন কিন্তু myFunction বা counter কোনটাই এক্সিকিউশন কন্টেক্সটে নেই, সব ডিলেট হয়ে গেছে। এখন সবকিছু কলিং কন্টেক্সটের কন্ট্রোলে চলে গেছে
  8. ৯নং লাইনে কলিং কন্টেক্সটের মধ্যে createCounter থেকে রিটার্ন হওয়ার ভ্যালুটা increment এর মধ্যে অ্যাসাইন হয়ে যাবে। increment ভ্যারিয়েবলটি এখন ফাংশন ডেফিনিশনটি ধারণ করে রেখেছে। এই ফাংশনটির নাম আর কোন ভাবেই createCounter নেই, কিন্তু ডেফিনিশনটা ঠিক আগের মতই আছে। গ্লোবাল কন্টেক্সটএ এটির নাম এখন increment
  9. ১০নং লাইনে আমরা c1 নামের একটি নতুন ভ্যারিয়েবল ডিক্লেয়ার করেছি
  10. ১০নং লাইনের increment ভ্যারিয়েবলটির দিকে একটু লক্ষ্য করুন, যেহেতু এটি একটি রিটার্ন ফাংশনকে রাখছে এবং এর একটি ফাংশন ডেফিনিশন আছে সেহেতু এটিও একটি ফাংশন। ফাংশনটিকে কল করা যাক
  11. ফাংশনটি কল করার সাথে সাথেই একটি নতুন এক্সিকিউশন কন্টেক্সটের ভেতর এই ফাংশনটি এক্সিকিউট করা শুরু করেছে
  12. ৪নং লাইনের counter ভ্যারিয়েবলটির দিকে লক্ষ্য করুন, এটি এর ভ্যালুর সাথে ১ করে যোগ করবে। এই ফাংশনটির ভেতরে কোথাও ভ্যালু আছে এমন কোন counter নামের ভ্যারিয়েবল খুঁজে পাওয়া যায় নি। জাভাস্ক্রিপ্ট এই (counter = undefined+1) ভ্যারিয়েবলটাকে undefined হিসেবে ধরে নিয়ে counter নামের একটি নতুন লোকাল ভ্যারিয়েবল ক্রিয়েট করবে এবং এর মধ্যে ভ্যালু হিসেবে 1 অ্যাসাইন করবে, যেহেতু undefined = 0
  13. ৫নং লাইনে আমরা counter ভ্যারিয়েবল = 1 রিটার্ন করেছি এবং সেই সাথে counter নামের লোকাল ভ্যারিয়েবল টাও এক্সিকিউশন কন্টেক্সট থেকে ডিলেট হয়ে গেছে
  14. ১০নং লাইনে আমরা increment() ফাংশন থেকে রিটার্ন করা ভ্যালু ১ কে c1 ভ্যারিয়েবলে আসাইন করলাম
  15. ঠিক একই ভাবে আরো দুইবার c2, c3 ভ্যারিয়েবলের মধ্যে increment() ফাংশনটাকে কল করে ফাংশন থেকে রিটার্ন করা ভ্যালু = 1 কে অ্যাসাইন করলাম। যে সিস্টেমে c1 এর ভ্যালু অ্যাসাইন হয়েছে, ঠিক একই ভাবে c2, c3 তে ভ্যালু অ্যাসাইন করা হয়েছে
  16. এবার ১৩ নং লাইনে আমরা c1, c2 এবং c3 ভ্যারিয়েবলের মান কনসোলে লগ করেছি

সবচেয়ে মজার বিষয় হল, আপনারা হয়তো ভাবছেন প্রোগ্রামটি 1,1,1 এরকম কোন অউটপুট দিবে। কিন্তু এটি অউটপুট হিসেবে প্রিন্ট করছে 1,2,3. কিন্তু কেন? জাভাস্ক্রিপ্টের কি মাথা খারাপ হয়ে গেল?

লক্ষ করুন increment ফাংশনটি কিন্তু কোন না কোনোভাবে counter ভ্যারিয়েবলের ভ্যালুটা মনে রাখছে। কিন্তু এটি কিভাবে সম্ভব?

তাহলে কি counter গ্লোবাল এক্সিকিউশন কন্টেক্সটের অংশ? console.log(counter) লিখে চেক করলে এটিও আমাদেরকে undefined দেখাচ্ছে। তাহলে বিষয়টা এটাও না।

হতে পারে যখনি আমরা increment ফাংশনটিকে কল করছি তখনই এটি (createCounter) ফাংশনের কাছে ফিরে যাচ্ছে?! কিন্তু সেটাও কিভাবে কাজ করবে? কারন increment ফাংশনটি একটি ফাংশন ডেফিনেশন রাখে কোন ভ্যালু রাখে না।

এখানে আরেকটি নতুন কনসেপ্ট কাজ করে। আর সেটি হল ক্লোজার। পরিশেষে আমরা আমাদের মিসিং অংশটাকে পেয়ে গেছি।

এখন বলছি এটি কিভাবে কাজ করে। যখনই আপনি একটি ফাংশনের ডেফিনেশন কে আরেকটি ভ্যারিয়েবলে রখবেন তখন তার সাথে একটি ক্লোজারও সেই ভ্যারিয়েবলে স্টোর হয়ে যাবে। একটি ফাংশন ক্রিয়েট করার সময়ই সেই স্কোপের সমস্ত ভ্যারিয়েবল ক্লোজারের মধ্যে স্টোর হয়ে যায়। তাই কোন ফাংশনের ডেফিনেশন যখন কোন ভ্যারিয়েবলে অ্যাসাইন করা হয় তখন সেই ফাংশনটি তার ডেফিনেশনের সাথে ক্লোজারটাকে ও প্যাকেট করে নিয়ে আসে।

আমাদের উপরের কোডের সমস্ত ব্যাখ্যা ভুল ছিল। চলুন নতুন করে এর সঠিক ব্যাখ্যাটা জেনে নেই।

1: function createCounter() {
2: let counter = 0
3: const myFunction = function() {
4: counter = counter + 1
5: return counter
6: }
7: return myFunction
8: }
9: const increment = createCounter()
10: const c1 = increment()
11: const c2 = increment()
12: const c3 = increment()
13: console.log('example increment', c1, c2, c3)
  1. ৩-৬ নং লাইনে প্রগ্রামটির লোকাল কনট্যাক্সটে myFunction নামের একটি লোকাল ভ্যারিয়েবল ডিক্লেয়ার করা হয়েছে। এই ভ্যারিয়েবলটির মান এখন অন্য একটি ফাংশনের ডেফিনিশন (যেমনটি ৪-৫ নং লাইনে দেখান হয়েছে )। এখন এখানে একটি ফাংশন ডেফিনেশনের সাথে সাথে একটি ক্লোজারও (স্টোরেজ) তৈরি হয়ে গেছে, যা বর্তমানে সেই ফাংশন ডেফিনেশনেরই একটি অংশ। ক্লোজার একটি স্টোরেজ মতো কাজ করে। একটি ক্লোজার তার স্কোপের সমস্ত ভ্যারিয়েবলকে নিজের মধ্যে ষ্টোর করে রাখে। এই প্রোগ্রামটির ক্ষেত্রে counter (counter = 0) ভ্যারিয়েবলটি ক্লোজারের মধ্যে ষ্টোর হয়ে গেছে
  2. যখনই আমরা ৭নং লাইনের লোকাল কন্টেক্সট থেকে muFunction ভ্যারিয়েবলের মান/ ভ্যালুটাকে রিটার্ন করছি, তখনি কিন্তু সাথে সাথেই লোকাল কন্টেক্সট ডিলেট হয়ে যাচ্ছে। এমনকি muFunction এবং counter নামের ভ্যারিয়েবলগুলো ও হারিয়ে যাচ্ছে। তবে মনে রাখতে হবে myFunction ভ্যারিয়েবলের ভ্যালুর সাথে তার একটি ক্লোজারও রিটার্ন হচ্ছে যার ভেতর আমাদের counter নামের ভ্যারিয়েবলটিও ষ্টোর করা রয়েছে
  3. ৯নং লাইনে আমাদের কলিং কন্টেক্সট বা গ্লোবাল কন্টেক্সটে createCounter থেকে রিটার্ন করা ভ্যালু increment ভ্যারিয়েবলের ভেতর অ্যাসাইন করা হয়েছে। এখন কিন্তু createCounter থেকে রিটার্ন করা ভ্যালুর সাথে একটি ক্লোজারও থাকছে। createCounter থেকে রিটার্ন করা ফাংশনের ডেফিনিশন (myFunction)+ ক্লোজার increment ভ্যারিয়েবলের ভিতর রাখার পর রিটার্ন করা myFunction নামক ফাংশনটি এখন গ্লোবাল কন্টেক্সটে increment নামে সেট করা হয়েছে। এখনও কিন্তু এর ফাংশন ডেফিনেশন আগের মতই আছে শুধু নামটা বদলে গেছে
  4. ১০নং লাইনে c1 নামের একটি নতুন ভ্যারিয়েবল ডিক্লেয়ার করা হল, যার মধ্যে increment নামের ভ্যারিয়েবলটি কল করা হল। কিন্তু লক্ষ্য করুন যেহেতু increment একটি ফাংশনের (myFunction) ডেফিনিশন রাখছে সেহেতু বর্তমানে increment ও এখন একটি ফাংশন
  5. এবার increment ফাংশনটিকে নতুন একটি এক্সেকিউশন কন্টেক্সটে এক্সেকিউট করা শুরু করি। এই ফাংশনটির প্যারামিটারে কোন আর্গুমেন্ট নিচ্ছে না। তাই সোজা ৪নং লাইনের counter নামক ভ্যারিয়েবলটির জন্য একটি ভ্যালু খুঁজতে হবে এবং তার সাথে 1 যোগ করে তার মান ফাংশন থেকে রিটার্ন করতে হবে। তাই counter ভ্যারিয়েবলটির মান লোকাল বা গ্লোবাল কন্টেক্সটে না খুঁজে আগে এর ক্লোজারের মধ্যে খুঁজবে। যেহেতু এই ফাংশনের আগে থেকেই একটি ক্লোজার ছিল, সেহেতু এর ভেতর থেকে ষ্টোর করা counter ভ্যারিয়েবলটিকে নিয়ে তার মানকে অর্থাৎ counter=0 এর সাথে 1 যোগ করবে (৪নং লাইন)। এবং সেই যোগফলটিকে ফাংশন থেকে রিটার্ন করবে (৫নং লইন)। এবার লোকাল কন্টেক্সটের কাজ শেষ
  6. এবার ১০নং লাইনের c1 ভ্যারিয়েবলে ভেতরের ফাংশন থেকে রিটার্ন করা counter ভ্যারিয়েবলের মান = 1 অ্যাসাইন করতে হবে, সাথে এর ক্লোজারের ভেতরের ভ্যারিয়েবলের নতুন মানটিও ষ্টোর হবে
  7. ১১নং লাইনও কিন্তু ১০ নং লাইনের মত ঠিক একই সিস্টেমে এক্সিকিউট হবে। এক্ষেত্রে যখনই আমরা ক্লোজারের মধ্যে counter ভ্যারিয়েবলের মান খুঁজতে যাব ঠিক তখনই দেখব এর মান এখন 1 সেট হয়ে আছে। এবং আবারও ৪নং লাইনের প্রোগ্রামে counter ভ্যারিয়েবলের মান 1 এর সাথে আর 1 যোগ হবে এবং ক্লোজারের নতুন মানটি আপডেট হবে (counter =2)। এখন যোগফলটির মান c2 ভ্যারিয়েবলে রাখা হবে
  8. ঠিক একই স্টেপে c3 ভ্যারিয়েবলের মান অ্যাসাইন করা হবে
  9. এবং সবশেষে ১৩ নং লাইনে গিয়ে যথাক্রমে c1, c2, c3 ভ্যারিয়েবলগুলোর মান কনসোলে প্রিন্ট হবে

এখন আমরা বুঝতে পারছি যে আসলে কিভাবে প্রোগ্রামের ভেতর ক্লোজার কাজ করে। সবসময় মনে রাখবেন — যখনই একটি ফাংশনকে ডিক্লেয়ার করা হয় তখন তার ভেতর একটি ফাংশন ডেফিনিশন এবং একটি ক্লোজার থাকে।

ক্লোজার হোল অনেকগুলো ভ্যারিয়েবল, তার স্কোপ থেকে নিয়ে এসে ষ্টোর করে রাখার একটি জায়গা, যেটি একটি ফাংশন তৈরি হবার সাথে সাথেই তৈরি হয়।

আপনি হয়ত ভাবছেন, যেকোন ফাংশনেরই কি ক্লোজার থাকা সম্ভব? অথবা গ্লোবাল স্কোপের ফাংশন গুলোর কি ক্লোজার থাকা সম্ভব?

হ্যাঁ, সম্ভব। গ্লোবাল স্কোপে ফাংশন তৈরি করার সময়ই তার সাথে একটি ক্লোজার যুক্ত হয়। কিন্তু যেহেতু ক্লোজারটি গ্লোবাল স্কোপে তৈরি হচ্ছে সেহেতু গ্লোবাল স্কোপে থাকা সমস্ত গ্লোবাল ভ্যারিয়েবল ক্লোজারের ভেতর থাকার কথা। কিন্তু এটি ক্লোজারের কনসেপ্টের সাথে যায় কি?

যখন একটি ফাংশন থেকে অন্য আরেকটি ফাংশন রিটার্ন করা হয় কেবল তখনই ক্লোজারের কন্সেপ্টটি যুক্তিযুক্ত। এক্ষেত্রে রিটার্ন করা ফাংশনটি যদিও গ্লোবাল স্কোপের ভেতরেরসমস্ত ভ্যারিয়েবলের আক্সেস নিতে পারে না ,কিন্তু তার ক্লোজারের ভেতর সেই ভ্যারিয়েবল গুলো আস্তে করে রয়ে যায়।

কম গুরুত্বপূর্ণ ক্লোজার

মাঝে মধ্যে তো এমন হয় যে ক্লোজারের কাজ ঘটে জাওয়ার পরও আমরা ধরতেই পারি না। আপনি হয়ত আমাদের parcial application নামের এক্সাম্পলটি দেখেছেন। যেমনটি নিচের কোডে বলে দেয়া আছে -

let c = 4
const addX = x => n => n + x
const addThree = addX(3)
let d = addThree(c)
console.log('example partial application', d)

Arrow function বাদেও কোডটিকে এভাবে দেখানো যায় -

let c = 4
function addX(x) {
return function(n) {
return n + x
}
}
const addThree = addX(3)
let d = addThree(c)
console.log('example partial application', d)

এখানে আমরা addx নামের একটি নরমাল ফাংশন ডিক্লেয়ার করেছি। এই ফাংশনটি একটি মাত্র আর্গুমেন্ট নেবে(x) এবং অন্য আরেকটি ফাংশনকে রিটার্ন করবে।

রিটার্নকৃত ফাংশনটিও একটি আর্গুমেন্ট নেবে(n) এবং সেটিকে পূর্বের ফাংশনের আর্গুমেন্ট (x) ভ্যারিয়েবলের সাথে যোগ করবে। অর্থাৎ n এবং x দুটি আর্গুমেন্ট যোগ করবে। এবং এই যোগফলকে ফাংশন থেকে রিটার্ন করবে। x ভ্যারিয়েবলটি এখন একটি ক্লোজারের অংশ। যখনই addThree নামক ভ্যারিয়েবলটি লোকাল এক্সেকিউশন কন্টেক্সটে ডিক্লেয়ার করা হবে তখনই এটি একটি ফাংশন এবং এর ক্লোজারকে অ্যাসাইন করবে। আর সেই ক্লোজারের ভেতর x ভ্যারিয়েবলের ভ্যালু রয়েছে।

তো যখনই addthree ভ্যারিয়েবলটি কল এবং এক্সেকিউট করা হবে, তখন ভেতরের ফাংশনটি সেই ক্লোজারের ভেতর থেকে x ভ্যারিয়েবলটিকে আক্সেস করতে পারবে। তারপর x ভ্যারিয়েবল এবং n ভ্যারিয়েবল (যেটাকে আর্গুমেন্ট হিসেবে পাস করা হয়েছে) যোগ করে যোগফলটাকে ফাংশন থেকে রিটার্ন করা হবে। এই এক্সাম্পলে কনসোলে যোগফলের মান 7 প্রিন্ট হবে।

পরিশেষে

আমি ক্লোজার মনে রাখার জন্য যে বিষয়টি মাথায় রাখি সেটি হল এর স্টোরেজ টেকনিক। যখনই একটি ফাংশন ক্রিয়েট করা হয় এবং পাস করা হয় অথবা অন্য কোন ফাংশন থেকে রিটার্ন করা হয় তখন ফাংশনটি তার সাথে একটি স্টোরেজ সিস্টেম রাখে। আর যখনই ফাংশনটিকে ডিক্লেয়ার করা হয় তখন সেই স্কোপে থাকা সমস্ত ভ্যারিয়েবলগুলো কে তার সেই স্টোরেজের (ক্লোজারের) মধ্যে ষ্টোর করে রাখে।

বিশেষ দ্রষ্টব্যঃ এটি একটি অনুবাদকৃত আর্টিকেল। এই আর্টিকেলটি অনুবাদ করা হয়েছে Olivier De Meulder এর I never understood JavaScript closures আর্টিকেলটি থেকে।

রেফারেন্সঃ

--

--