The Mysterious ‘this’ in Javascript (Translated)

HM Nayem
Stack Learner
Published in
8 min readMar 8, 2020

--

আপনি যদি কখনো জাভাস্ক্রিপ্টের লাইব্রেরী ব্যবহার করে কোন কিছু তৈরি করে থাকেন তাহলে হয়ত অবশ্যই ‘this’ কিওয়ার্ডটি লক্ষ্য করেছেন। জাভাস্ক্রিপ্টে this খুবই একটা কমন টপিক্স। বেশ কিছু ডেভেলপার অনেক সময় ধরেই পুরোপুরি এই কনসেপ্টটাকে বোঝার চেষ্টা করেছেন যে আসলে কিভাবে this কিওয়ার্ড কাজ করে থাকে এবং কোথায় এটা ব্যবহৃত হয়।

এই পোস্টে this এবং এর কার্যকারিতা সম্পর্কে বিস্তারিত বোঝানোর চেষ্টা করব

তবে শুরু করার পূর্বে দেখে নিন আপনার সিস্টেমে Node ইন্সটল করা আছে কিনা? যদি না থাকে তাহলে https://nodejs.org/en/ এখান থেকে Node ইন্সটল করে নিন। তারপরে আপনার কমান্ড লাইন বা টার্মিনালে node কমান্ডটা রান করুন।

this এবং Global Environment

this এর কাজ করার সিস্টেম বোঝা মোটেও সহজ কোন কাজ নয়। this কিভাবে কাজ করে সেটা আমরা ভিন্ন ভিন্ন এনভাইরমেন্টে ব্যবহার করে দেখব। চলুন দেখে আসি this, global এনভাইরমেন্টে কিভাবে কাজ করে থাকে।

Global এনভারনমেন্ট এর ক্ষেত্রে this মানেই হচ্ছে Global অবজেক্ট।

this === global // true
// or in browser
this === window // true

কিন্তু এটি শুধু মাত্র Node Shell এর জন্যই প্রযোজ্য। আমরা ওপরের কোডটিকেই যদি একটা জাভাস্ক্রিপ্ট ফাইলে রেখে Node এনভাইরমেন্টে রান করি তাহলে আউটপুট পেতাম false।এটা পরীক্ষা করে দেখার জন্য ওপরের কোডটিকে index.js ফাইলের ভিতরে রেখে Node কমান্ড ব্যবহার করে ফাইলটা কে রান করি।

node index.js  // false

এখানে false আউটপুট আসার কারণ হচ্ছে NodeJS এনভাইরমেন্টে জাভাস্ক্রিপ্ট ফাইলে this এর ভ্যালু module.export, Global না। আর এটা শুধু Node এনভাইরমেন্টের জন্যই প্রযোজ্য।

ফাংশনের ভিতরে this

একটি ফাংশনের ভিতরে this এর ভ্যালু সাধারণত ফাংশন কল করে ডিফাইন করা হয়। (ফাংশন ইনভোক হওয়ার পূর্ব মুহুর্ত পর্যন্ত this এর ভ্যালু অ্যাসাইন হয় না)। তাই একটি ফাংশনের ভিতরে this এর ভ্যালু প্রত্যেকবার এক্সিকিউশনের জন্য ভিন্ন ভিন্ন হতে পারে।

পূর্বে তৈরি করা index.js ফাইলে একটি সাধারণ ফাংশন লিখুন যেটা শুধুমাত্র ফাংশনের this টা Global অবজেক্ট কিনা সেটা পরীক্ষা করে দেখবে।

function test() {  
console.log(this === global);
}
test();

আমরা যদি নোড ব্যবহার করে ওপরের কোডটা রান করি তাহলে আউটপুট true পাব। কিন্তু আমরা যদি ফাইলের ওপরে use strict লিখে দেই তাহলে আমরা false আউটপুট পাব। কারণ এখন this এর ভ্যালু undefined। বিষয়টাকে আর একটু পরিষ্কার করতে একটা নতুন ফাংশন তৈরি করা যাক, যেখানে একটি সুপার হিরোর রিয়েল নেম এবং হিরো নেমকে ডিফাইন করবে।

function Hero(heroName, realName) {  
this.heroName = heroName;
this.realName = realName;
}
const superman = Hero('Superman', 'Clark Kent'); console.log(superman);

লক্ষ্য করবেন আমাদের এই ফাংশনটি কিন্তু strict mode এ লেখা হচ্ছে না। কোডটিকে Node এ রান করলে দেখবেন এটি আশানরূপ superman এর realName ‘Superman’ এবং heroName ‘Clark Kent’ আউটপুট দিচ্ছে না। তার বদলে আউটপুট দিচ্ছে undefined।

যেহেতু আমাদের কোডটি strict mode এ রান করা হয়নি, সেহেতু এই this টি Global অবজেক্ট কে রেফার করছে। যদি সেম কোডটিকে আমরা Strict mode এ রান করি তাহলে আমরা ইরোর পাব। কারণ জাভাস্ক্রিপ্ট আমাদেরকে undefined কোন কিছুর ভিতরে কোন প্রোপার্টি অ্যাাসাইন করতে দেয় না। এটা অবশ্য আমাদের জন্য ভাল কারণ এটা আমাদেরকে Global ভ্যারিয়েবল ক্রিয়েট করা থেকে বিরত রাখে।

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

const superman = new Hero('Superman', 'Clark Kent'); console.log(superman);

এবং node index.js কমান্ডটা রান করুন। আপনি আপনার প্রত্যাশিত আউটপুট পেয়ে যাবেন।

Constructor এর ভিতরে this

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

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

Hero ফাংশন এর ভিতরে নিচের কোডের মত রিটার্ন স্টেটমেন্টটি লিখুন।

function Hero(heroName, realName) {  
this.heroName = heroName;
this.realName = realName;
return {
heroName: 'Batman',
realName: 'Bruce Wayne'
};
}
const superman = new Hero('Superman', 'Clark Kent'); console.log(superman);

এখন যদি আমরা node index.js কমান্ডটা রান করি তাহলে দেখব ওপরের রিটার্ন স্টেটমেন্টটা আমাদের কন্সট্রাক্টর কলকে ওভাররাইড করে ফেলেছে। যদি রিটার্ন স্টেটমেন্টটা কোন অবজেক্টকে রিটার্ন না করে অন্য কোন কিছু রিটার্ন করে সেই ক্ষেত্রে রিটার্ন স্টেটমেন্ট কন্সট্রাক্টর কলকে ওভাররাইড করবে না। সেই ক্ষেত্রে অবজেক্টটা তার অরিজিনাল ভ্যালু বহন করবে।

Method এর ভিতরে this

যখন কোন ফাংশনকে অবজেক্টের মেথড হিসেবে কল করা হয়, তখন this সেই অবজেক্টকে রেফার করে। তখন সেই অবজেক্টকে ফাংশন কলের রিসিভার বলা হয়।

নিচের কোডে আমার hero অবজেক্টের মধ্যে একটা dialouge নামের মেথড আছে। এখানে dialouge এর this টা hero অবজেক্টকে রেফার করছে। তাই এখানে hero অবজেক্টটা dialouge মেথড কলের রিসিভার।

const hero = {  
heroName: 'Batman',
dialouge() {
console.log(`I am ${this.heroName}`);
}
};
hero.dialouge();

এটা একটা খুবই সাধারণ উদাহরণ। কিন্তু রিয়েল ওয়ার্ল্ডে কাজের ক্ষেত্রে রিসিভার গুলোকে ট্রেস করা মেথডের পক্ষে খুবই কঠিন কাজ।

index.js ফাইলের শেষের দিকে নিচের দুই লাইন কোড লেখা যাক।

const saying = hero.dialouge; 
saying();

এখানে আমি dialouge মেথডের রেফারেন্সটাকে একটি ভ্যারিয়েবল এর ভিতরে রাখছি এবং সেই ভ্যারিয়েবলটাকে ফাংশন হিসেবে কল করছি। node এ ওপরের কোডটি রান করলে দেখবেন এখানে this undefined রিটার্ন করছে কারণ dialouge মেথডটি তার রিসিভার এর ট্রাক হারিয়ে ফেলেছে। এখন এই মেথডের this টি Global কে রেফার করছে যেখানে hero কে রেফার করার কথা ছিল।

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

call() এবং apply()

যদিও একটি ফাংশনের this এর ভ্যালু ইমপ্লিসিট ভাবেই সেট করা থাকে, তারপরেও আমরা চাইলে call() এবং apply() ফাংশন ব্যবহার করে বাইরে থেকে this এর ভ্যালু সেট করে দিতে পারি। পূর্বের কোডটিকে নিচের কোডের মত পরিবর্তন করে দেখা যাক।

function dialouge() {     
console.log(`I am ${this.heroName}`)
}
const hero = {
heroName: 'Batman'
}

আমাদের hero অবজেক্টকে রিসিভার হিসেবে dialouge ফাংশনের সাথে কানেক্ট করা দরকার। এই কাজটা করার জন্য আমরা call() অথবা apply() ফাংশনের সাহায্য নিতে পারি।

dialouge.call(hero) 
// or
dialouge.apply(hero)

কিন্তু strict mode এর বাইরে যদি আমরা call() বা apply() ফাংশন ব্যবহার করি এবং সেখানে null বা undefined ভ্যালু পাস করি তাহলে জাভাস্ক্রিপ্ট ইঞ্জিন এটাকে ইগ্নোর করবে। strict mode ব্যাবহার করার এটা অন্যতম একটা কারণ।

bind()

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

bind() মেথড ব্যবহার করে একটা this আর্গুমেন্টকে একটা পার্মানেন্ট ভ্যালু হিসেবে সেট করতে পারি। নিচের কোডের দিকে লক্ষ্য করলেই দেখা যাবে bind() একটি নতুন dialouge ফাংশন তৈরি করবে এবং এর this ভ্যালুকে hero অবজেক্ট এর ভ্যালু হিসেবে সেট করবে।

const hero = {  
heroName: 'Batman',
dialouge() {
console.log(`I am ${this.heroName}`);
}
};
setTimeout(hero.dialouge.bind(hero), 1000)

এতে করে আমাদের this এর ভ্যালুকে কখনোই call() বা apply() মেথড দিয়ে পরিবর্তন করা সম্ভব নয়।

Arrow ফাংশনের ভিতরে this

জাভাস্ক্রিপ্টে যেকোনো ফাংশনে this ব্যবহার করা আর arrow ফাংশনে this ব্যবহার করার মধ্যে অনেক পার্থক্য রয়েছে। Arrow ফাংশনের ভিতরে this ব্যবহার করা হলে এটা সেই ফাংশনের ভিতরকার context কে this এর ভ্যালু হিসেবে ধরে নেয়। যদিও this এর একটি নিজস্ব global ভ্যালু আছে, arrow ফাংশন পার্মানেন্টলি this এর ভ্যালুকে এমন ভাবে সেট করে, যেটাকে call() বা apply() মেথড দিয়েও পরবর্তীতে পরিবর্তন করা সম্ভব হয় না।

এই ক্ষেত্রে arrow ফাংশনের ভিতরে this কিভাবে কাজ করে সেটা বোঝার জন্য নিচের কোডটি লক্ষ্য করুন।

const batman = this;  
const bruce = () => {
console.log(this === batman);
};
bruce();

এখানে আমরা একটি ভ্যারিয়েবলের ভিতরে this এর ভ্যালুকে রেখে দিলাম। তারপর একটি arrow ফাংশনের ভিতরের this এর ভ্যালুকে বাইরের ভ্যারিয়েবলের this এর ভ্যালু এর সাথে তুলনা করলাম। কোডটি রান করার পরে আমাদের টার্মিনালে true আউটপুট আসার কথা। Arrow ফাংশনের ভিতরে this এর ভ্যালুকে সরাসরি সেট করে দেওয়া যায় না। আবার যদি বাইরে থেকে call(), apply() বা bind() মেথড ব্যবহার করে this এর ভ্যালু পাস করার চেষ্টা করা হয় তাহলে সেই ভ্যালুকে arrow ফাংশন ইগনোর করবে। অর্থাৎ arrow ফাংশনের বাইরে থেকে কোন ভাবেই this এর ভ্যালুকে পরিবর্তন করা যায় না। যখন একটি arrow ফাংশন তৈরি হয় শুধুমাত্র তখনই this এর ভ্যালু সেট হয়ে যায়।

Arrow ফাংশনকে কখনোই কন্সট্রাক্টর হিসেবে ব্যবহার করা যায় না। যেহেতু arrow ফাংশনের this এ কোন প্রোপার্টি যুক্ত করা যায় না, তাহলে arrow ফাংশনের এই this ঠিক কি কাজে লাগে?

Arrow ফাংশন আমাদেরকে একটি কলব্যাক ফাংশনের this কে এক্সেস করতে সাহায্য করে। এটি কিভাবে করে জানতে নিচের কাউন্টার অবজেক্ট এর দিকে লক্ষ্য করুন -

const counter = {  
count: 0,
increase() {
setInterval(function () {
console.log(++this.count);
}, 1000);
}
};
counter.increase();

এই কোডটিকে রান করলে এটা আমাদেরকে শুধুমাত্র NaN এর একটি লম্বা লিস্ট দিবে। কারণ এখানে this.count সরাসরি counter অবজেক্টকে রেফার করছে না। এটা আসলে রেফার করছে Global অবজেক্টকে। এই counter টিকে সঠিক ভাবে কাজ করাতে হলে কোডটিকে arrow ফাংশন ব্যবহার করে সাজাতে হবে।

const counter = {  
count: 0,
increase() {
setInterval(() => {
console.log(++this.count);
}, 1000);
}
};
counter.increase();

আমাদের কলব্যাক ফাংশনটি এখন increase মেথড এর সাথে bind করা this কে ব্যবহার করছে এবং এখন কিন্তু counter যেমনটি কাজ করার কথা ছিল ঠিক তেমনই করছে।

নোটঃ ++this.count এর পরিবর্তে this.count + 1 লিখলে কাজ করবে না। কারণ এখানে counter টা শুধুমাত্র এর ভ্যালুকে এক করে বৃদ্ধি করবে এবং প্রত্যেকবার একটি করে নতুন ভ্যালু রিটার্ন করবে।

Class এর ভিতরে this

যেকোনো জাভাস্ক্রিপ্ট অ্যাপ্লিকেশনে class হচ্ছে সব থেকে গুরুত্বপূর্ণ একটি অংশ। চলুন দেখা যাক this কিভাবে class এর ভিতরে কাজ করে।

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

চলুন Hero নামের নতুন একটি Class তৈরি করি যা আগে আমরা ফাংশন আকারে দেখেছিলাম। এই class এ থাকবে একটি কন্সট্রাক্টর এবং dialouge মেথড। সব শেষে আমরা class এর একটি ইন্সট্যান্স তৈরি করে dialouge মেথডকে কল করব।

class Hero {
constructor(heroName) {
this.heroName = heroName;
}
dialouge() {
console.log(`I am ${this.heroName}`);
}
}
const batman = new Hero('Batman');
batman.dialouge();

এখানে কন্সট্রাক্টরের ভিতরের this, class এর একটি নতুন ইন্সট্যান্সকে রেফার করছে। যখনই আমরা batman.dialouge() মেথডকে কল করছি, তখনই আমরা dialouge কে মেথড হিসেবে এবং batman কে এর রিসিভার হিসেবে ব্যবহার করছি। কিন্তু আমরা যদি dialouge মেথডের রেফারেন্সটা কোথাও স্টোর করে রাখি এবং পরে সেটাকে ফাংশন হিসেবে কল করি তাহলে আমরা আবারো মেথডের রিসিভার হারাবো। তার সাথে এটি this কেও undefined রেফার করবে।

const say = batman.dialouge
say()

এই ইরোর এর কারণ হল জাভাস্ক্রিপ্টে class গুলো ইমপ্লিসিট ভাবেই strict mode এ থাকে। আমরা say() ফাংশনকে কল করছি কোন অটোবাইন্ডিং ছাড়াই। এই সমস্যাটাকে সমাধান করতে হলে আমাদেরকে dialouge ফাংশনকে বাইরে থেকে batman এর সাথে বাইন্ড করে দিতে হবে। আমরা এই bind() টা class এর কন্সট্রাক্টরের ভিতরেও করতে পারতাম।

const say = batman.dialouge.bind(batman);
say();

পরিশেষে

জাভাস্কিপ্টে this keyword কে অনেকটা ইংলিশ এর pronoun এর মত করে ব্যবহার করতে হবে। যেমন -

  • Shegufa Taranjum loves DC Comics
  • Shegufa Taranjum also loves Marvel Movies

এই দুইটা বাক্যকে একসাথে বলতে গেলে আমরা বলবো, Shegufa loves DC Comics and she also loves Marvel Movies। এই ছোট্ট একটা গ্রামার থেকে আমরা জাভাস্ক্রিপ্টে this এর গুরুত্বটা বুঝতে পারছি। এখানে she দুইটা বাক্যকে যুক্ত করছে। ঠিক একই ভাবে জাভাস্ক্রিপ্টে this ও একটি শর্টকাট রেফারেন্স হিসেবে কাজ করে থাকে।

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

আর তারপরেও যদি বুঝতে সমস্যা হয় তাহলে Stack Learner ইউটিউব চ্যানেল থেকে this এবং অবজেক্ট অরিয়েন্টেড জাভাস্কিপ্টের পূর্নাঙ্গ প্লেলিস্ট দেখে নিতে পারেন। নিচে রেফারেন্সে লিংক দেওয়া রইল।

বিশেষ দ্রষ্টব্যঃ এটি একটি অনুবাদকৃত আর্টিকেল। এই আর্টিকেলটি অনুবাদ করা হয়েছে Rajat S এর What is “this” in JavaScript? আর্টিকেলটি থেকে।

রেফারেন্সঃ

--

--

HM Nayem
Stack Learner

Fullstack Web Developer, Entrepreneur and Trainer