Advance JavaScript শিখুন বাংলায় (পর্ব ৩) — this কীওয়ার্ড বিস্তারিত বোঝা

this কীওয়ার্ড - JavaScript কনটেক্সট

আগের পর্বে আমরা Scope আর Hoisting শিখেছিলাম। এই পর্বে আমরা দেখব JavaScript-এর সবচেয়ে বেশি ভুল বোঝা একটা কীওয়ার্ড — this। অন্য ভাষার মতো this সবসময় একই জিনিস বোঝায় না — এটা নির্ভর করে ফাংশনটা কীভাবে কল করা হচ্ছে তার উপর।

মূল নিয়ম — this নির্ভর করে কল করার ধরনের উপর

this-এর মান ফাংশন কোথায় লেখা হয়েছে তার উপর না, বরং ফাংশনটা কোথা থেকে কল করা হচ্ছে তার উপর নির্ভর করে (arrow function বাদে, যেটা একটু ভিন্ন — নিচে দেখব)।

কল করার ধরন this কী হয়
object.method() object
standaloneFunction() undefined (strict mode) বা window
new Constructor() নতুন তৈরি হওয়া অবজেক্ট
Arrow function বাইরের (surrounding) স্কোপের this
call()/apply()/bind() যা তুমি স্পষ্টভাবে দিয়েছ

Method হিসেবে কল করা

যখন একটা ফাংশনকে অবজেক্টের প্রপার্টি হিসেবে কল করা হয়, this সেই অবজেক্টকে রেফার করে।

const user = {
  name: "আরিফ",
  greet() {
    console.log("আমার নাম " + this.name);
  }
};

user.greet(); // "আমার নাম আরিফ" — this === user

সতর্কতা: মেথডটা যদি অবজেক্ট থেকে "আলাদা" হয়ে যায় (যেমন একটা ভ্যারিয়েবলে রাখা), তাহলে this হারিয়ে যায়:

const greetFn = user.greet;
greetFn(); // ❌ "আমার নাম undefined" — this আর user না, কারণ এখন standalone কল

Arrow Function — ভিন্ন নিয়ম

Arrow function-এর নিজের কোনো this নেই — এটা তার চারপাশের (lexical) স্কোপ থেকে this ধার নেয়। এটা প্রায়ই সমস্যার সমাধান দেয়:

const timer = {
  seconds: 0,

  // ❌ সাধারণ ফাংশন — setInterval-এর ভেতরে this হারিয়ে যায়
  startBuggy() {
    setInterval(function () {
      this.seconds++; // this এখানে timer না! (undefined বা window)
      console.log(this.seconds); // NaN বা error
    }, 1000);
  },

  // ✅ Arrow function — বাইরের this (timer) ব্যবহার করে
  startFixed() {
    setInterval(() => {
      this.seconds++; // this এখানে সঠিকভাবে timer
      console.log(this.seconds);
    }, 1000);
  }
};

startFixed-এ arrow function this-কে নিজে থেকে না বদলে বাইরের startFixed মেথডের this (যেটা timer) সেটাই ব্যবহার করছে — এই কারণেই ইভেন্ট হ্যান্ডলার আর কলব্যাকে আজকাল arrow function-ই বেশি ব্যবহার করা হয়।

লাইভ ডেমো — নরমাল ফাংশন বনাম Arrow Function

নিচের বাটনে চাপো — দুটো ভিন্ন আচরণ দেখো: একটা কাউন্টার সঠিকভাবে বাড়বে (arrow function দিয়ে), আরেকটা "ভেঙে" যাবে (সাধারণ ফাংশনে this হারিয়ে)।

❌ সাধারণ ফাংশন

0

✅ Arrow Function

0

call, apply, bind দিয়ে this নিয়ন্ত্রণ করা

এই তিনটা মেথড দিয়ে স্পষ্টভাবে this কী হবে সেটা ঠিক করে দেওয়া যায়।

function introduce(city) {
  console.log(`আমি ${this.name}, থাকি ${city}-তে`);
}

const person = { name: "আরিফ" };

// call — আর্গুমেন্ট আলাদা করে দেওয়া হয়
introduce.call(person, "ঢাকা"); // "আমি আরিফ, থাকি ঢাকা-তে"

// apply — আর্গুমেন্ট অ্যারে হিসেবে দেওয়া হয়
introduce.apply(person, ["ঢাকা"]); // একই ফলাফল

// bind — নতুন একটা ফাংশন তৈরি করে, this চিরকালের জন্য বেঁধে দেয়
const boundIntroduce = introduce.bind(person);
boundIntroduce("ঢাকা"); // একই ফলাফল, পরেও যতবার কল করো this পাল্টাবে না
মেথড কী করে
call(obj, a, b) সাথে সাথে কল করে, আর্গুমেন্ট আলাদা করে
apply(obj, [a, b]) সাথে সাথে কল করে, আর্গুমেন্ট অ্যারেতে
bind(obj) সাথে সাথে কল করে না, নতুন ফাংশন রিটার্ন করে

DOM ইভেন্টে this

ইভেন্ট লিসেনারে সাধারণ ফাংশন ব্যবহার করলে this সেই এলিমেন্টকেই রেফার করে যেটাতে ইভেন্ট ঘটেছে:

button.addEventListener("click", function () {
  console.log(this); // এই <button> এলিমেন্ট
  this.style.background = "red";
});

// কিন্তু arrow function ব্যবহার করলে this আসবে বাইরের স্কোপ থেকে
button.addEventListener("click", () => {
  console.log(this); // বাটন না, বরং যেখানে এই কোড লেখা হয়েছে তার this
});

টিপস: ইভেন্ট হ্যান্ডলারে যদি ক্লিক করা এলিমেন্টটাই দরকার হয়, সাধারণ ফাংশন ব্যবহার করো (বা e.target)। যদি বাইরের অবজেক্ট/ক্লাসের ডেটা দরকার হয়, arrow function ব্যবহার করো।

একটা সম্পূর্ণ উদাহরণ — ক্লাসে this

ক্লাসের মেথডে this নিয়ে সমস্যা প্রায়ই ইভেন্ট লিসেনারে বাঁধিয়ে দেওয়ার সময় হয়:

class Counter {
  constructor() {
    this.count = 0;
    this.button = document.getElementById("countBtn");

    // ❌ সমস্যা: this.increment কল হলে this আর Counter instance থাকবে না
    // this.button.addEventListener("click", this.increment);

    // ✅ সমাধান ১: arrow function দিয়ে wrap করা
    this.button.addEventListener("click", () => this.increment());

    // ✅ সমাধান ২: bind() ব্যবহার করা
    // this.button.addEventListener("click", this.increment.bind(this));
  }

  increment() {
    this.count++;
    console.log(this.count);
  }
}

new Counter();

উপরে কমেন্ট করা লাইনটা (this.increment সরাসরি পাস করা) কাজ করবে না, কারণ ফাংশনটা Counter থেকে "আলাদা" হয়ে যাচ্ছে যখন ইভেন্ট লিসেনারে পাস করা হচ্ছে — এটাই আগে দেখা "method বিচ্ছিন্ন হওয়া" সমস্যা।

নিজে চেষ্টা করো

Exercise:

  1. একটা অবজেক্ট বানাও যার একটা মেথড আছে, সেটা একটা ভ্যারিয়েবলে আলাদা করে রাখো, তারপর কল করে দেখো this কী হয়ে যায়।
  2. উপরের timer উদাহরণটা নিজে বানাও — startBuggy আর startFixed দুটোই রান করে পার্থক্য দেখো।
  3. bind() ব্যবহার করে একটা ফাংশনকে একটা নির্দিষ্ট অবজেক্টে "লক" করে দাও, তারপর সেটা অন্য অবজেক্টে কল করার চেষ্টা করেও দেখো এখনো আগের অবজেক্টই ব্যবহার হচ্ছে কিনা।
  4. উপরের Counter ক্লাসটা বানাও, একটা বাস্তব বাটনের সাথে যুক্ত করো, তারপর কমেন্ট করা লাইনটা আনকমেন্ট করে দেখো কী এরর আসে।

পরের পর্বে যা থাকছে

পরের পর্বে আমরা শিখব Destructuring — অ্যারে আর অবজেক্ট থেকে সহজে ভ্যারিয়েবল বের করে নেওয়ার আধুনিক সিনট্যাক্স, যা কোড অনেক পরিষ্কার করে তোলে।

একটি মন্তব্য পোস্ট করুন

নবীনতর পূর্বতন

যোগাযোগ ফর্ম