2.8 জাভাস্ক্রিপ্টে ক্লোজার

 

Closure কোন ফাংশন না আবার ফাংশনও কোন closure না। Closure হচ্ছে ফাংশনের এমন একটা বৈশিষ্ট্য যে বৈশিষ্ট্যের কারণে ফাংশন এক্সিকিউশন শেষ হয়ে যাবার পরেও তার lexical scope এ অবস্থিত সকল variable কে মনে রাখতে পারে। উদাহরণস্বরূপ বলা যেতে পারে যে ডম থেকে কিছু অ্যাক্সেস করার জন্যে আমরা যে ইভেন্ট ফাইয়ার করি সেটাও একটা closure।

কিছু উদাহরণঃ

function add(a) {
  return function (b) {
    return a + b;
  };
}

let addTen = add(10);
let addSeven = addTen(7);

console.log(addSeven); // 17

কি হচ্ছে এসব?? ঠিক আছে, চলেন দেখি কোডগুলোকে ভেঙ্গেঃ-

১। যখন add ফাংশনটি কল হয় এটি আরেকটি ফাংশনকে return করে।
২। ঐ ফাংশনটির এক্সিকিউশন শেষ হয়ে যায় এবং মনে রাখে ঐ সময় তার প্যারামিটার a এর ভ্যালু কি ছিল।
৩। যখন addTen ভেরিয়েবলে add ফাংশনকে এসাইন করা হয়। এটি সব সময় মনে রাখবে a এর ভ্যালু কি ছিল যখন এটিকে ইনিশিয়ালি কল করা হয়েছিল।
৪। উপরের addTen ভেরিয়েবল একটি ফাংশনকে বোঝায় যেটি সব সময় ভ্যালু ১০ যোগ করবে যা পাঠানো হয়েছিল।
৫। তার মানে হল যখন addTen কে কল করা হয় ৭ ভ্যালু দিয়ে, এটি ১০ এর সাথে ৭ যোগ করবে এবং ১৭ রিটার্ন করবে।

সুতরাং, জাভাস্ক্রিপ্ট ইঞ্জিন addTen কে যেভাবে রান করেঃ-

function addTen(b) {
  return 10 + b;
}

এখন একটা মজার উদাহরণ দেখবো। কিভাবে আমরা লুপের ভিতরে ক্লোজার চালাতে পারি। এটি ইন্টার্ভিউ বোর্ডের একটা কমন প্রশ্ন। নিচের কোডটা দেখেন এবং একটু মনে মনে চিন্তা করেন এটার আউটপুট কত হবে।

for (var i = 1; i <= 5; i++) {
  setTimeout(() => console.log(i), 1000);
}

// কাঙ্ক্ষিত আউটপুটঃ -
// 1
// 2
// 3
// 4
// 5

// কিন্তু আসছে অনাকাঙ্ক্ষিত আউটপুটঃ- 
// 6
// 6
// 6
// 6
// 6

আসলে এই আউটপুট আসার অনেক কারণ আছে। লুপের মাঝে ভ্যারিয়েবল i হচ্ছে একটি গ্লোবাল ভ্যারিয়েবল। যখন setTimeout রান হয় তার আগেই লুপ শেষ হয়ে যায় এবং তাই i ভ্যালু 6 হয়ে যায়। সেজন্যে প্রতি এক সেকন্ড পর পর পাঁচবার 6 দেখাচ্ছে। যদি বিশ্বাস না হয় তাহলে কোডটা রান করার পর আপনার গ্লোবাল window অবজেক্টটা একবার দেখেন সেখানে i নামে একটা ভ্যারিয়েবল দেখতে পারবেন এবং তার ভ্যালু 6 হয়ে আছে।

এই সমস্যার সমাধান আমরা IIFE বা Immediately Invoked Function Expression ব্যবহার করে করতে পারি। নিচে উদাহরণ দেওয়া হলঃ-

পদ্ধতি ১ঃ
for (var i = 1; i <= 5; i++) {
  (function () {
    var val = i;
    setTimeout(() => console.log(val), 1000);
  })();
}
পদ্ধতি ২ঃ
for (var i = 1; i <= 5; i++) {
  (function (val) {
    setTimeout(() => console.log(val), 1000);
  })(i);
}

এখানে আমরা একটা ফাংশন লিখে একটা Scope তৈরি করেছি। ফাংশনটিকে ইমিডিয়েটলি কল করেছি এবং তার প্যারামিটারের ভ্যালু হিসাবে i কে পাস করেছি। এতে সে এখন i এর ভ্যালুকে মনে না রেখে সে এখন তার প্যারামিটারের ভ্যালুকে  মনে রাখবে। মানে এখন i এর মান 1, 2 করে যাচ্ছে এবং সেটা থেকে একটা আলাদা Scope তৈরি হচ্ছে যেটাকে সে মনে রাখছে।

পদ্ধতি ৩ঃ
for (let i = 1; i <= 5; i++) {
  setTimeout(() => console.log(i), 1000);
}

// Output: 
// 1
// 2
// 3
// 4
// 5

অবশেষে আমাদের কাঙ্ক্ষিত আউটপুট পেলাম। তবে আজ এই পর্যন্তই কথা হবে পরবর্তীতে অন্য কোনো টপিক নিয়ে।

উদাহরন
  • for (var count = 1; count <= 5; count++) {
      setTimeout(() => console.log(count), 1000);
    }
    
    // Output:
    //  6
    //  6
    //  6
    //  6
    //  6

    var কীওয়ার্ড ফাংসন স্কোপ । তাই যখন আমরা ব্যবহার করে ভেরিয়েবল তৈরি করি একই ভেরিয়েবল নামের var কীওয়ার্ড, এটি সবসময় একই ভেরিয়েবলের দিকে নির্দেশ করে। এখানে আমরা অ্যাসিঙ্ক্রোনাস ফাংশন সেটটাইমআউট ব্যবহার করি। সময় শেষ হলে টাইমআউটের পরে অ্যাসিঙ্ক্রোনাসভাবে কার্যকর করা হয়, গণনার মান ইতিমধ্যে 6 হয়ে গেছে। কারণ এটি 5 পর্যন্ত লুপ চালিয়ে যাবে এবং তারপরে আরেকটি ইনক্রিমেন্ট অপারেশন কাউন্টের মান 6 এ বৃদ্ধি করে। তাই এটি পাঁচবারের জন্য 6 প্রিন্ট করবে আমরা ক্লোজার ব্যবহার করে এই সমস্যা সমাধান করতে পারি ।

  • সেল্ফ ইনভোকিং ফাংশনে ক্লোজার
    const add = (function () {
      let counter = 0;
      return function () {
        counter += 1;
        return counter;
      };
    })();
    
    add();
    add();
    add();
  • Every closure has three scopes:
    Local Scope (Own scope)
    Outer Functions Scope
    Global Scope

    Here’s a series of nested functions, all of which have access to the outer functions’ scope. In this context, we can say that closures have access to all outer function scopes.

    // global scope
    var e = 10;
    function sum(a) {
      return function (b) {
        return function (c) {
          // outer functions scope
          return function (d) {
            // local scope
            return a + b + c + d + e;
          };
        };
      };
    }
    
    console.log(sum(1)(2)(3)(4)); // log 20

     

এসো নিজে করি
  • জাভাস্ক্রিপ্টে ক্লোজার কি?
  • জাভাস্ক্রিপ্টে ক্লোজার সুবিধা কি?
  • নিচের কোডের আউটপুট কি হবে?

    const multiply = (x) => {
      return function (y) {
        return x * y;
      };
    };
    
    let multiply10 = multiply(10);
    let multiplyFive = multiply10(5);
    
    console.log(multiplyFive);
  • নিচের কোডের আউটপুট কি হবে?
    for (var count = 1; count <= 5; count++) {
      setTimeout(() => console.log(count), 1000);
    }