এই পর্বে যা শিখবেন
- JavaScript-এ
thisকেন “কে ডাকল” দিয়ে ঠিক হয়, আর C++/Java/Python-এর সাথে পার্থক্য কোথায় - চারটি binding নিয়ম আর তাদের অগ্রাধিকার — যেকোনো লাইনের
thisচার প্রশ্নে বের করা - Arrow function-এর lexical
this, আর method-কে callback হিসেবে পাঠালেthisহারানোর সমস্যা ও তার সমাধান call,applyআরbind-এর পার্থক্য — method ধার করা, partial application, bound function-এর নিয়ম- বিভিন্ন জায়গায় (script, module, class, event handler, timer)
thisকী, আর নিজে একটি ছোটbindবানানো
১ · this আসলে কী — “কে ডাকছে” প্রশ্ন
পর্ব ১৪-এ আপনি method লিখেছেন আর দেখেছেন, method-এর ভেতরে this দিয়ে object নিজের ডেটা পড়ে: cart.total()-এর ভেতরে this.items. সেখানে একটি সাবধানবাণীও ছিল — method-কে আলাদা করে নিলে this হারায়। এই পর্বে সেই রহস্যের পুরো সমাধান। শুরুতে একটি বড় ধারণা পরিষ্কার করে নেওয়া দরকার।
বেশিরভাগ object-oriented ভাষায় — C++, Java, C# — this মানে “এই কোড যে object-এর, সেই object”. Method একটি class-এর ভেতরে লেখা থাকে, আর compiler জানে method-টি কোন ধরনের object-এর। তাই this-এর অর্থ লেখার জায়গা দেখেই ঠিক হয়ে যায়।
JavaScript-এ ব্যাপারটি আলাদা। এখানে function একটি স্বাধীন মান (first-class function, পর্ব ১৩)। একই function কোনো object-এর property হতে পারে, আরেকটি object-এর property-ও হতে পারে, variable-এ থাকতে পারে, অন্য function-এ পাঠানো যেতে পারে। Function নিজে জানে না সে “কার”. তাই JavaScript সিদ্ধান্তটি নেয় ডাকার মুহূর্তে: সাধারণ function-এর ভেতরে this কী হবে, তা ঠিক হয় function-টিকে কীভাবে ডাকা হলো তা দেখে — কোথায় লেখা হয়েছে তা দেখে নয়।
চায়ের দোকানে একজন বললেন, “আমার বিলটা দিন।” দোকানি কার বিল দেবেন? যিনি বললেন, তাঁর। একই বাক্য আরেকজন বললে অন্য বিল। “আমার” শব্দটির কোনো স্থির মালিক নেই — মালিক ঠিক হয় কে বলছে তা দিয়ে। সাধারণ JavaScript function-এর this ঠিক এই “আমার” শব্দের মতো। আর arrow function যেন একজন সহকারী, যিনি নিজে কখনো “আমার” বলেন না — তিনি সবসময় যাঁর সাথে এসেছেন, তাঁর বিলটাই চান।
এই একটি বাক্যে পুরো পর্বের সারকথা: সাধারণ function-এর this = ডাকার ধরন দেখে; arrow function-এর this = চারপাশের কোড দেখে। সাধারণ function-এর জন্য চারটি নিয়ম আছে, আর arrow-এর জন্য একটি। এই পাঁচটি নিয়ম দিয়ে this-এর প্রতিটি “অদ্ভুত” আচরণ ব্যাখ্যা করা যায় — কোনো ব্যতিক্রম মুখস্থ করতে হয় না।
📞 প্রশ্নটি “কোথায়” নয়, “কীভাবে”
একই function greet() — riya.greet(), greet(), greet.call(nila) — তিনবার ডাকলে তিন রকম this. Function বদলায়নি, বদলেছে ডাকার ধরন।
🏹 Arrow-এর this স্থির
Arrow function তৈরি হওয়ার সময় চারপাশে যে this ছিল, সারাজীবন সেটিই। এটি একটি সাধারণ variable খোঁজার মতো — lexical scope ধরে বাইরে খোঁজা।
⚠️ this scope-এর variable নয়
Closure (পর্ব ২২) variable মনে রাখে, কিন্তু সাধারণ function-এর this মনে রাখে না — প্রতিবার ডাকার সময় নতুন করে ঠিক হয়। এটিই নতুনদের সবচেয়ে বড় বিভ্রান্তি।
C++ আর Java-তে this class দিয়ে স্থির — method যে class-এ লেখা, this সেই class-এর object, আর তা কখনো “হারায়” না (Java-র constructor আর this). Python-এ self স্পষ্ট parameter, আর obj.method লিখলেই একটি “bound method” তৈরি হয় যা self মনে রাখে — তাই method আলাদা করে নিলেও কাজ করে (Python-এর class আর self). JavaScript-এ this লুকানো parameter-এর মতো, কিন্তু প্রতিবার ডাকার সময় নতুন করে বসে — obj.method কোনো বন্ধন তৈরি করে না। C জানলে ভাবুন: যেন প্রতিটি function-এর একটি গোপন প্রথম argument আছে, যা কলকারী নিজে বসায়। এগুলোর কোনোটি না জানলে বাক্সটি বাদ দিন।
২ · চারটি নিয়ম আর তাদের অগ্রাধিকার
একটি সাধারণ function (arrow নয়) যেকোনো লাইনে ডাকা হলে, this বের করতে নিচের চারটি প্রশ্ন ক্রমানুসারে করুন। যে প্রশ্নের উত্তর প্রথমে “হ্যাঁ”, সেটিই জিতবে:
| ক্রম | প্রশ্ন | নিয়মের নাম | this কী | উদাহরণ |
|---|---|---|---|---|
| ১ | new দিয়ে ডাকা হয়েছে? | new binding | সদ্য তৈরি নতুন object | new Shop("Rahim") |
| ২ | call, apply বা bind দিয়ে object দেওয়া হয়েছে? | explicit binding | যে object দেওয়া হলো | greet.call(nila) |
| ৩ | obj.method() আকারে, ডট বা bracket দিয়ে ডাকা? | implicit binding | ডটের বাঁ পাশের object | riya.greet() |
| ৪ | ওপরের কোনোটিই না | default binding | strict mode-এ undefined; সাধারণ script-এ global object | greet() |
আর এই চার প্রশ্নের আগে একটি “শূন্যতম” প্রশ্ন আছে: function-টি কি arrow? হলে চারটি নিয়মের কোনোটিই খাটে না — arrow-এর this সবসময় চারপাশের কোডের this (অংশ ৫)। এখন সিদ্ধান্তের ধাপগুলো চারটি বাস্তব ডাকের ওপর চালিয়ে দেখুন:
- ১. new দিয়ে?
new Shop(…)হ্যাঁ → নতুন object - ২. call/apply/bind?
f.call(obj)হ্যাঁ → দেওয়া object - ৩. obj.method()?
riya.greet()হ্যাঁ → ডটের বাঁ পাশ - ৪. default
greet()strict → undefined
function greet() { "use strict"; return this; }
function Shop(name) { this.name = name; }
const riya = { name: "Riya", greet };
const nila = { name: "Nila" };
প্রশ্ন ১: new আছে? হ্যাঁ
→ engine একটি খালি object বানাল, this = সেই object
→ this.name = "Rahim Store"; object-টিই ফেরত
প্রশ্ন ১: new? না
প্রশ্ন ২: call দিয়ে object দেওয়া? হ্যাঁ — nila
→ ডটের বাঁ পাশে riya থাকলেও this = nila
প্রশ্ন ১: না · প্রশ্ন ২: না
প্রশ্ন ৩: obj.method() আকার? হ্যাঁ — ডটের বাঁ পাশে riya
→ this = riya
প্রশ্ন ১, ২, ৩: সব না → default binding: function-এ "use strict" আছে → this = undefined // strict না হলে global object
- একই function, চারটি ডাক।
greetশুধু নিজেরthisফেরত দেয়। আমরা চারভাবে ডাকব আর প্রতিবার ছকের প্রশ্নগুলো ওপর থেকে নিচে করব। মনে রাখুন, প্রশ্নের ক্রমই অগ্রাধিকার। new— সবার আগে।newদেখলেই engine একটি নতুন খালি object বানায়,this-কে সেটির সাথে বাঁধে, function চালায়, আর (function অন্য কোনো object ফেরত না দিলে) সেই object ফেরত দেয়। new আর constructor-এর পুরো গল্প পর্ব ২৬-এ।- Explicit — হাতে দেওয়া object.
callবলে দেয় “এবারthisহবে nila”. লক্ষ করুন, ডটের বাঁ পাশেriyaআছে, তবুও explicit binding implicit-কে হারায় — কারণ প্রশ্ন ২ আগে আসে। - Implicit — ডটের বাঁ পাশ। কোনো
newনেই, কোনোcallনেই, কিন্তু ডাকটিriya.greet()আকারে। তাইthis=riya. সবচেয়ে প্রচলিত নিয়ম, আর পর্ব ১৪-এ এটিই শিখেছিলেন। - Default — কিছুই না মিললে। খালি
greet(). Function-টি strict mode-এ, তাইthisহলোundefined. Strict না হলে (সাধারণ script-এ)thisহতো global object — ব্রাউজারেwindow. পরের অংশে এর কারণ।
অগ্রাধিকার: new > explicit (call/apply/bind) > implicit (obj.method()) > default. আর arrow function এই তালিকার বাইরে — তার this কেউ বদলাতে পারে না। একটি সূক্ষ্মতা: bind দিয়ে বানানো function-কে new দিয়ে ডাকলে new জেতে, কিন্তু call দিয়ে ডাকলে bind জেতে।
চারটি নিয়ম আর হারানো this — নিজে চালিয়ে দেখুন। Output নিশ্চিত রাখতে function-এর ভেতরে "use strict" দেওয়া হয়েছে, যাতে default binding-এ undefined আসে:
function whoAmI() {
"use strict"; // default binding → undefined
return this === undefined ? "undefined" : this.name;
}
const riya = { name: "Riya", whoAmI };
const nila = { name: "Nila" };
console.log("১ implicit:", riya.whoAmI());
console.log("২ default:", whoAmI());
console.log("৩ explicit:", riya.whoAmI.call(nila));
const lost = riya.whoAmI; // শুধু function নেওয়া হলো
console.log("৪ হারানো:", lost());
const toNila = whoAmI.bind(nila);
console.log("৫ bind-এর পরে call:", toNila.call(riya));
console.log("৬ আবার bind:", toNila.bind(riya)());
function Shop(name) { this.name = name; }
const NilaShop = Shop.bind(nila);
console.log("৭ new:", new NilaShop("Rahim Store").name, nila.name);ফলাফল: ১ implicit: Riya, ২ default: undefined, ৩ explicit: Nila, ৪ হারানো: undefined, ৫ bind-এর পরে call: Nila, ৬ আবার bind: Nila, ৭ new: Rahim Store Nila. লাইন ৪ মন দিয়ে দেখুন: lost আর riya.whoAmI হুবহু একই function, শুধু ডাকার ধরন আলাদা — তাই ফলও আলাদা। লাইন ৫ আর ৬ দেখায়, bind করা function-এর this পরে call বা আরেকটি bind দিয়ে বদলানো যায় না — প্রথম bind-ই জেতে। আর লাইন ৭-এ new bind-কেও হারিয়েছে: নতুন object-এর নাম “Rahim Store”, আর nila.name অক্ষত।
৩ · Default binding — strict mode, sloppy mode আর globalThis
চতুর্থ নিয়মটি সবচেয়ে বেশি বিভ্রান্তি তৈরি করে, কারণ এর উত্তর দুই রকম। JavaScript-এর দুটি “মোড” আছে। পুরোনো, ঢিলেঢালা নিয়মের মোডকে অনানুষ্ঠানিকভাবে বলে sloppy mode; আর ES5 (২০০৯) যোগ করেছে strict mode, যা চালু হয় ফাইল বা function-এর শুরুতে "use strict" লিখলে। ES module (পর্ব ৪৫) আর class-এর ভেতরের সব কোড নিজে থেকেই strict.
😴 Sloppy mode — global object
খালি f() ডাকলে this হয় global object (ব্রাউজারে window). this.x = 5 লিখলে চুপচাপ একটি global variable তৈরি হয়। f.call(null) বা f.call(undefined) দিলেও global object, আর f.call(5) দিলে this হয় একটি Number object (মোড়ানো মান)।
🛡️ Strict mode — undefined
খালি f() ডাকলে this = undefined. this.x পড়লে সাথে সাথে TypeError — ভুলটি লুকায় না। call-এ যা দেবেন, হুবহু তা-ই: null দিলে null, 5 দিলে primitive 5.
কেন strict mode-এ undefined? ধরুন কেউ একটি constructor function new ছাড়া ডাকলেন: const s = Shop("Rahim"). Sloppy mode-এ this হয় window, তাই this.name = "Rahim" লেখে window.name-এ — পুরো পাতার একটি global মান নষ্ট হলো, আর কোনো error নেই। Strict mode-এ this undefined, তাই সাথে সাথে Edge/Chrome বলে TypeError: Cannot set properties of undefined (setting 'name'). ভুল সামনে আসে। নিরাপত্তার কারণও আছে — ভাবনার প্রশ্ন ৩-এ।
globalThis — সব জায়গায় global object-এর একই নাম
Global object-এর নাম জায়গাভেদে আলাদা: ব্রাউজারের পাতায় window, Web Worker-এ self, Node.js-এ global. সব জায়গায় চলে এমন কোড লিখতে আগে মানুষ জটিল কৌশল লিখত। ES2020-এ যুক্ত হয়েছে globalThis — যেকোনো runtime-এ global object-এর একটিই নাম। তাই global object দরকার হলে this-এর ওপর ভরসা না করে সরাসরি globalThis লিখুন।
এই কোর্সের runnable উদাহরণ সম্পর্কে একটি কথা। পাতার ভেতরের JavaScript runner কোড চালায় একটি সাধারণ (classic) script হিসেবে — অর্থাৎ sloppy mode-এ, যদি না আপনি নিজে "use strict" লেখেন। তাই এই পর্বের উদাহরণে function-এর ভেতরে "use strict" দেওয়া, যাতে default binding-এ undefined আসে আর output সবার কাছে একই থাকে। বাস্তব প্রজেক্টে আপনি প্রায় সবসময় module বা class লিখবেন, যেখানে strict আপনাআপনি।
একটি ছোট কিন্তু গুরুত্বপূর্ণ খুঁটিনাটি: strict কি না, তা ঠিক হয় যে function ডাকা হচ্ছে তার কোড দেখে, কলকারীর কোড দেখে নয়। Strict কোড থেকে একটি sloppy function ডাকলে সেই function-এর ভেতরে default this তখনো global object.
৪ · Implicit binding আর “হারানো this”
Implicit binding-এর শর্তটি খুব আক্ষরিক: ডাকার মুহূর্তে function-এর নামের আগে একটি ডট (বা bracket) আর একটি object থাকতে হবে। riya.greet() — হ্যাঁ। riya["greet"]() — হ্যাঁ। কিন্তু function-টি একবার object থেকে বের করে নিলে, পরে যেখানেই ডাকুন, ডটের বাঁ পাশে আর কেউ থাকে না। এটিই lost this বা “হারানো this” সমস্যা। ধাপে ধাপে দেখুন:
1const cart = {2 owner: "Riya",3 show() { "use strict"; return this?.owner; },4};5console.log(cart.show());6const f = cart.show;7console.log(f());8const g = cart.show.bind(cart);9console.log(g());
| cart | |
| f | |
| g | |
| show-এর this |
- Method আসলে একটি আলাদা function.
showobject-এর “ভেতরে” থাকে না; object-এরshowproperty-তে থাকে শুধু একটি function-এর reference — ধরুন তার নাম F. পর্ব ১৪-এর reference-এর ধারণা এখানেও খাটে। cart.show()— implicit. ডাকার মুহূর্তে ডটের বাঁ পাশেcart, তাইthis=cart, ফলRiya.const f = cart.show— শুধু function কপি। এখানে কোনো ডাক নেই (বন্ধনী নেই)।f-এ গেল F-এর reference — object-এর সাথে কোনো সম্পর্ক নয়।f === cart.showসত্য।f()— default! ডাকার মুহূর্তে ডটের বাঁ পাশে কেউ নেই, তাই চতুর্থ নিয়ম।showstrict, তাইthis=undefined.this?.ownerতাইundefinedদিল।?.না থাকলে এখানে TypeError হতো।bind— নতুন, বাঁধা function.cart.show.bind(cart)F-কে বদলায় না; একটি নতুন bound function তৈরি করে, যার ভেতরে লেখা থাকে “ডাকলে F চালাবে,this= cart দিয়ে”.g()— ডট ছাড়াও ঠিক।g-কে খালি ডাকলেও ভেতরে explicit binding হয়, তাই আবারRiya. শিক্ষা: function যখন object ছেড়ে অন্য কোথাও যাবে, হয় তাকে bind করে পাঠান, নয় একটি arrow-এ মুড়ে পাঠান।
কোথায় কোথায় this হারায়
বাস্তবে কেউ const f = cart.show খুব কম লেখেন। কিন্তু একই ঘটনা আরও তিনটি জায়গায় ঘটে, যেগুলো চোখে পড়ে না, কারণ সেখানে function-টিকে callback হিসেবে অন্য কারও হাতে দেওয়া হয় — আর সে নিজের ইচ্ছামতো ডাকে:
- Timer:
setTimeout(cart.report, 1000). Timer-কে দেওয়া হলো শুধু function. ব্রাউজার পরে সেটিকে ডাকেthisহিসেবে global object দিয়ে (HTML মানদণ্ড অনুযায়ী — strict function হলেও); Node.js-এthisহয় একটিTimeoutobject. দুই ক্ষেত্রেইcartনয়। - Array method:
orders.forEach(printer.print).forEachcallback-কে ডাকেthis=undefinedদিয়ে (দ্বিতীয় argument না দিলে) — strict-এundefined, sloppy-তে global object. - Event listener:
button.addEventListener("click", counter.increment). ব্রাউজার সাধারণ function handler-কে ডাকেthis= যে element-এ listener বসানো, সেটি দিয়ে —counterনয়,button! এটি পর্ব ৩৮-এ আরও দেখবেন।
নিয়মটি সবগুলোতে একই: আপনি function দিয়েছেন, ডাকছে অন্য কেউ — তাই this ঠিক করছে সে। চারটি প্রচলিত সমাধান:
| সমাধান | লেখার ধরন | কখন ভালো |
|---|---|---|
| Arrow wrapper | setTimeout(() => cart.report(), 1000) | সবচেয়ে পরিষ্কার; ডাকটি আবার obj.method() আকারে হয় |
bind | setTimeout(cart.report.bind(cart), 1000) | একই বাঁধা function বারবার লাগলে, বা পরে removeEventListener-এ একই reference দরকার হলে |
| thisArg | orders.forEach(printer.print, printer) | forEach, map, filter, some, every, find — সবাই দ্বিতীয় argument হিসেবে this নেয় |
| Class-এর arrow field | report = () => { … } | Class-এর method সবসময় callback হিসেবে যাবে জানলে (পর্ব ২৬) |
removeEventListener-এর ফাঁদ। btn.addEventListener("click", this.onClick.bind(this)) লিখে পরে btn.removeEventListener("click", this.onClick.bind(this)) লিখলে listener সরে না — কারণ প্রতিবার bind একটি নতুন function বানায়, আর দুটি আলাদা function-এর reference কখনো সমান নয়। সমাধান: বাঁধা function একবার বানিয়ে একটি variable বা property-তে রাখুন, আর দুই জায়গায় সেটিই দিন।
৫ · Arrow function — নিজের this নেই, চারপাশ থেকে ধার
পর্ব ১৩-এ দেখেছেন, arrow function-এর নিজের this নেই। এর মানে খুব নির্দিষ্ট: arrow-এর ভেতরে this লিখলে engine সেটিকে একটি সাধারণ variable-এর মতো খোঁজে — arrow যেখানে লেখা, তার চারপাশের scope-এ, তারপর আরও বাইরে। একে বলে lexical this. সাধারণ function-এর this ঠিক হয় ডাকার সময়; arrow-এর this ঠিক হয়ে যায় arrow তৈরির সময়, আর আর কখনো বদলায় না।
এর সবচেয়ে বড় উপকার method-এর ভেতরের callback-এ। নিচের stepper-এ একই কাজ দুইভাবে লেখা — একবার arrow callback, একবার সাধারণ function callback:
1const team = {2 name: "Tigers",3 players: ["Riya", "Nila"],4 withArrow() {5 return this.players.map((p) => p + " · " + this.name);6 },7 withFunction() {8 "use strict";9 return this.players.map(function (p) { return this; });10 },11};12team.withArrow();13team.withFunction();
| বাইরের method-এর this | |
| ভেতরের callback-এর this | |
| ফল |
team.withArrow(). Implicit binding —withArrow-এর ভেতরেthis=team. এ পর্যন্ত নতুন কিছু নেই।- Arrow callback-এর
this.maparrow-টিকে ডাকে। কিন্তু arrow-এর নিজেরthisনেই, তাই সে বাইরে তাকায় — যে method-এর ভেতরে লেখা, সেইwithArrow-এরthis, অর্থাৎteam.mapকীভাবে ডাকল, তা arrow-এর কাছে অপ্রাসঙ্গিক। ফল[ 'Riya · Tigers', 'Nila · Tigers' ]. team.withFunction(). আবার implicit binding,this=team. বাইরের স্তরে সব ঠিক।- সাধারণ function callback — হারাল! ভেতরের
function (p)-এর নিজস্বthisআছে, আর সেটি ঠিক করে যে ডাকে —map.mapতাকে খালি ডাকে (thisArg ছাড়া), তাই default binding: strict-এundefined. বাইরের method-এরthisযেteam, তাতে কিছু যায় আসে না। ফল[ undefined, undefined ]. - তিনটি সমাধান। আধুনিক উপায়: callback-টিকে arrow করুন। অথবা
map-এর দ্বিতীয় argument-এthisদিন। ES2015-এর আগে মানুষ লিখতconst self = this;আর ভেতরেself.name— closure দিয়েthis-এর মান ধরে রাখা (ভাবনার প্রশ্ন ২)।
Arrow-এর চারটি “নেই”
- নিজের
thisনেই — আরcall,apply,bindদিয়ে দেওয়াthisচুপচাপ উপেক্ষিত হয় (argument-গুলো অবশ্য যায়)। কোনো error আসে না, তাই ভুলটি ধরা কঠিন। newদিয়ে ডাকা যায় না —const A = () => {}; new A()দেয়TypeError: A is not a constructor. Arrow-এরprototypeproperty-ও নেই।- নিজের arguments নেই — arrow-এর ভেতরে
argumentsলিখলে বাইরের সাধারণ function-এর arguments পাবেন (বা script-এর global স্তরে ReferenceError). Rest parameter(...args)ব্যবহার করুন। - নিজের
superআরnew.targetনেই — এগুলোও বাইরে থেকে আসে; class-এর প্রসঙ্গে পর্ব ২৬-এ।
Object-এর method নিজে arrow — সবচেয়ে প্রচলিত ভুল। const cart = { owner: "Riya", greet: () => this.owner }. মনে হয় this = cart, কিন্তু object literal-এর বাঁকা বন্ধনী কোনো scope তৈরি করে না। Arrow-টি লেখা আছে script বা module-এর একদম বাইরের স্তরে, তাই সেখানকার this পায় — module-এ undefined (তখন TypeError), classic script-এ window (তখন চুপচাপ undefined). নিয়ম: object-এর method লিখুন shorthand দিয়ে; arrow রাখুন method-এর ভেতরের callback-এর জন্য।
৬ · call, apply আর bind — this হাতে বসানো
JavaScript-এ function নিজেও একটি object, আর প্রতিটি সাধারণ function Function.prototype থেকে তিনটি method পায় (prototype কী, তা পর্ব ২৫-এ)। তিনটিরই কাজ this হাতে ঠিক করে দেওয়া, কিন্তু ধরন আলাদা:
| Method | লেখার ধরন | Argument কীভাবে | ফল |
|---|---|---|---|
| call | f.call(obj, a, b) | একটি একটি করে, কমা দিয়ে | এখনই চালায়, ফল ফেরত দেয় |
| apply | f.apply(obj, [a, b]) | একটি array (বা array-এর মতো object) হিসেবে | এখনই চালায়, ফল ফেরত দেয় |
| bind | f.bind(obj, a) | কিছু আগেই বসিয়ে রাখা যায়, বাকিটা পরে | চালায় না; নতুন bound function ফেরত দেয় |
মনে রাখার সহজ কৌশল: apply-এর “a” = array, call-এর “c” = comma. আর bind = “পরে চালানোর জন্য বেঁধে রাখো”।
Method ধার করা
call-এর সবচেয়ে সুন্দর ব্যবহার হলো একটি object-এর method আরেকটি object-এর ওপর চালানো — method borrowing. ধরুন একটি দোকানের দাম দেখানোর method আছে, আর আরেকটি দোকানের নিজস্ব মুদ্রা চিহ্ন আছে কিন্তু method নেই। কপি না করে ধার করুন: shop.label.call(otherShop, 120). Method-টি যদি শুধু this-এর মাধ্যমে ডেটা পড়ে, তাহলে যে object-ই দিন, তার ডেটা দিয়েই কাজ করবে।
পুরোনো কোডে একটি বিখ্যাত ধার দেখবেন: Array.prototype.slice.call(arguments). arguments একটি array-like object — length আর সংখ্যার key আছে, কিন্তু array-এর method নেই। Array-এর slice শুধু this.length আর this[0], this[1]… পড়ে, তাই তাকে arguments-এর ওপর চালালে একটি আসল array বেরিয়ে আসে। আজ একই কাজের জন্য আছে Array.from(arguments), spread [...arguments], বা শুরু থেকেই rest parameter.
apply বনাম spread
ES2015-এর আগে একটি array-এর সবচেয়ে বড় মান বের করার প্রচলিত উপায় ছিল Math.max.apply(null, prices) — Math.max আলাদা আলাদা argument চায়, আর apply array-কে argument-এ ভেঙে দেয়। this এখানে অপ্রাসঙ্গিক, তাই null. এখন একই কাজ Math.max(...prices) — পড়তে সহজ (পর্ব ১৭)। তাই আধুনিক কোডে apply কম দেখা যায়; প্রধানত তখনই, যখন this আর array-রূপের argument দুটোই একসাথে দিতে হয়। একটি সতর্কতা: দুই পদ্ধতিতেই প্রতিটি উপাদান একটি আলাদা argument হয়, আর engine-এর argument সংখ্যার একটি সীমা আছে — লক্ষাধিক উপাদানের array-এ RangeError আসতে পারে; তখন reduce বা লুপ নিরাপদ।
bind দিয়ে partial application
bind-এর প্রথম argument this, আর তার পরের argument-গুলো আগেই বসিয়ে রাখা যায়। পরে bound function ডাকলে নতুন argument-গুলো সেগুলোর পেছনে যোগ হয়। পর্ব ২৩-এ যে partial application দেখেছেন, এটি তারই একটি built-in রূপ। যেমন একটি logger, যার প্রতিটি লাইনের শুরুতে একটি স্থির উপসর্গ (prefix) থাকবে: const log = console.log.bind(console, "[cart]"). এখন log("টাইমার বসানো হলো") ছাপে [cart] টাইমার বসানো হলো. this দরকার না হলে প্রথম argument-এ null দেওয়া প্রচলিত: const addVat = add.bind(null, 15).
Bound function-এর তিনটি নিয়ম
একটি bound function আসল function-এর ওপর একটি পাতলা মোড়ক। তার name হয় "bound whoAmI"-এর মতো, আর length থেকে আগে বসানো argument-এর সংখ্যা বাদ যায়। আর তার this কতটা শক্ত, তা ধাপে ধাপে দেখুন:
1function show() { "use strict"; return this?.name; }2const a = { name: "A" }, b = { name: "B" };3const fa = show.bind(a);4console.log(fa());5console.log(fa.call(b));6const fab = fa.bind(b);7console.log(fab());8const o = { name: "O", fa };9console.log(o.fa());
| fa | |
| fab | |
| show-এর আসল this |
- একটি function, দুটি object.
showনিজেরthis-এরnameফেরত দেয়। এখনো কিছু বাঁধা হয়নি। show.bind(a). নতুন bound functionfaতৈরি হলো। ভেতরে দুটি তথ্য স্থায়ীভাবে লেখা: “আসল function = show” আর “this = a”.showনিজে বদলায়নি।fa(). খালি ডাক, কিন্তুfaনিজেই ভেতরেshow-কেaদিয়ে ডাকে। ফলA.fa.call(b)— call হেরে গেল।callfa-কেthis=bদিয়ে ডাকে ঠিকই, কিন্তুfaসেইthisব্যবহারই করে না — সে সবসময় নিজের লেখাaদিয়েshowডাকে। ফল আবারA.fa.bind(b)— প্রথম bind-ই জেতে। দ্বিতীয় bindfa-এর ওপর আরেকটি মোড়ক বানায় (this = b), কিন্তু ভেতরেরfaআবারওaদিয়েইshowডাকে। দ্বিতীয় bind শুধু বাড়তি argument যোগ করতে পারে,thisবদলাতে পারে না। ফলA.o.fa()— implicit-ও হারে।fa-কে অন্য object-এ বসিয়ে ডটসহ ডাকলেও একই কথা: ফলA. একমাত্র যা bind-কে হারায়, তা হলোnew— অংশ ২-এর runnable উদাহরণের লাইন ৭.
এবার বাস্তব কাজ — একটি কার্ট object, যার method timer-এ গিয়ে this হারায়, দুটি সমাধান, method ধার করা, apply বনাম spread আর উপসর্গ বাঁধা logger:
const cart = {
owner: "Riya", items: [80, 120, 45],
report() {
console.log(`রিপোর্ট: ${this?.owner ?? "কেউ না (this হারিয়েছে)"}`);
},
totalLater() {
setTimeout(() => { // arrow: totalLater-এর this ধার করে
const sum = this.items.reduce((s, p) => s + p, 0);
console.log(`${this.owner}-এর মোট ${sum} টাকা`);
}, 40);
},
};
setTimeout(cart.report, 10); // ❌ শুধু function গেল
setTimeout(() => cart.report(), 20); // ✅ arrow wrapper
setTimeout(cart.report.bind(cart), 30); // ✅ bind
cart.totalLater();
const shop = { cur: "৳", label(p) { return this.cur + p; } };
console.log(shop.label.call({ cur: "$" }, 120)); // method ধার
console.log(Math.max.apply(null, cart.items), Math.max(...cart.items));
const log = console.log.bind(console, "[cart]");
log("টাইমার বসানো হলো");আগে তিনটি সঙ্গে সঙ্গে চলা লাইন: $120 (ধার করা label নতুন object-এর মুদ্রা দেখাল), 120 120 (apply আর spread একই ফল), [cart] টাইমার বসানো হলো. তারপর timer-গুলো সময়ের ক্রমে: রিপোর্ট: কেউ না (this হারিয়েছে) — ব্রাউজার timer callback-কে window দিয়ে ডেকেছে, যার owner নেই; তারপর দুবার রিপোর্ট: Riya; শেষে Riya-এর মোট 245 টাকা — totalLater-এর ভেতরের arrow তার this ধার করেছে। এই arrow-টি যদি সাধারণ function হতো, শেষ লাইনে this.items পড়তে গিয়ে TypeError আসত।
৭ · বিভিন্ন জায়গায় this — একটি পূর্ণ মানচিত্র
চারটি নিয়ম আর arrow-এর নিয়ম মিলিয়ে এখন যেকোনো জায়গার this বলা যায়। এক টেবিলে সব:
| কোথায় | this কী | কোন নিয়মে |
|---|---|---|
| Classic script-এর একদম বাইরের স্তর | global object (window) | script-এর নিজস্ব নিয়ম |
| ES module-এর বাইরের স্তর | undefined | module সবসময় strict |
| Node.js CommonJS ফাইলের বাইরের স্তর | module.exports (শুরুতে {}) | Node ফাইলটিকে একটি function-এ মুড়ে চালায় |
| সাধারণ function, খালি ডাক | strict: undefined; sloppy: global object | default |
obj.method() | obj | implicit |
f.call(x) / f.apply(x) / bound function | x | explicit |
new F() | নতুন object | new |
| Arrow function | যেখানে লেখা, সেখানকার this | lexical |
Class-এর method (obj.method() দিয়ে ডাকলে) | সেই object; খালি ডাকলে undefined | implicit; class সবসময় strict |
Class-এর static method (Cart.create()) | class নিজে (Cart) | implicit — ডটের বাঁ পাশে class |
Class field-এর initializer (items = []) | তৈরি হতে থাকা নতুন object | constructor-এর সময় চলে |
addEventListener-এ সাধারণ function | যে element-এ listener বসানো (e.currentTarget) | ব্রাউজার explicit-ভাবে বসায় |
addEventListener-এ arrow | বাইরের this | lexical |
setTimeout-এ সাধারণ function | ব্রাউজারে global object; Node.js-এ Timeout object | timer যেভাবে ডাকে |
একটি “পছন্দ” বোতাম — event handler-এর this
নিচের কোডটি এই পাতায় চালানোর জন্য নয় — DOM আর event-এর বিস্তারিত পর্ব ৩৭ আর ৩৮-এ। শুধু this কোথায় কী, তা দেখুন:
// HTML: <button id="likeBtn">পছন্দ (0)</button>
const counter = {
count: 0,
button: document.querySelector("#likeBtn"),
start() {
this.button.addEventListener("click", function () {
this.count++; // ❌ এখানে this = button, counter নয় → button.count = NaN
});
this.button.addEventListener("click", () => {
this.count++; // ✅ arrow: start-এর this = counter
this.button.textContent = `পছন্দ (${this.count})`;
});
},
};
counter.start();প্রথম handler সাধারণ function, তাই ব্রাউজার তাকে this = button দিয়ে ডাকে; button.count আগে ছিল না (undefined), ++ করলে NaN. দ্বিতীয়টি arrow, তাই start-এর this ধার করে — আর start ডাকা হয়েছিল counter.start() দিয়ে। একটি ক্লিকে বোতামে লেখা হয় “পছন্দ (1)”. কখনো কখনো element-টিই দরকার — যেমন একটিমাত্র handler দশটি বোতামে বসিয়ে যেটি চাপা হলো সেটির রং বদলানো। তখন সাধারণ function-এর this সুবিধাজনক, তবে আধুনিক কোডে e.currentTarget লেখাই বেশি স্পষ্ট।
Class-এর arrow field — এক ঝলক
Class-এর method prototype-এ থাকে আর সব object ভাগ করে নেয় (পর্ব ২৫, ২৬); তাই callback হিসেবে পাঠালে সাধারণ method-এর মতোই this হারায়। ES2022-এর class field দিয়ে একটি সমাধান প্রচলিত হয়েছে:
class Cart {
items = [];
add = (item) => { this.items.push(item); }; // প্রতিটি object-এর নিজের arrow
clear() { this.items = []; } // prototype-এ — আলাদা করলে this হারায়
}
const c = new Cart();
["চাল", "ডাল"].forEach(c.add); // ✅ কাজ করে
// setTimeout(c.clear, 0); // ❌ class strict → this undefined → TypeErrorField initializer constructor-এর সময় চলে, যখন this = নতুন object; arrow সেটিই চিরতরে ধরে রাখে। খরচ: প্রতিটি object-এর জন্য আলাদা একটি function তৈরি হয় (prototype-এ একটি ভাগের function নয়), আর subclass super.add() দিয়ে এটিকে ডাকতে পারে না। তাই শুধু যে method-গুলো সত্যিই callback হিসেবে যাবে, সেগুলোতেই এটি ব্যবহার করুন।
const f = obj.method; f();—thisকী? — default binding: strict-এundefined, sloppy-তে global object;objনয়, কারণ ডাকার সময় ডটের বাঁ পাশে কেউ নেই।call,apply,bind-এর পার্থক্য? —callargument কমা দিয়ে নেয়,applyarray হিসেবে; দুটোই এখনই চালায়।bindচালায় না, স্থায়ীthis-সহ নতুন function ফেরত দেয়।- Arrow function-কে
callদিয়েthisদেওয়া যায়? — না; arrow-এর নিজেরthisনেই, দেওয়া মান উপেক্ষিত হয়।newদিয়েও ডাকা যায় না। - একটি function দুবার bind করলে কোন
thisথাকে? — প্রথমটি; পরের bind শুধু argument যোগ করতে পারে। bound function-কেnewদিয়ে ডাকলে অবশ্য bind-এরthisউপেক্ষিত হয়। - Object literal-এ
{ f: () => this }—thisকী? — object নয়; object literal scope তৈরি করে না, তাই বাইরের স্তরেরthis(module-এundefined, classic script-এwindow).
৮ · নিজে একটি bind বানানো — ভেতরে কী ঘটে
কোনো built-in জিনিস সত্যিই বুঝেছেন কি না, তা যাচাইয়ের ভালো উপায় হলো নিজে একটি ছোট সংস্করণ লেখা। ES5 (২০০৯)-এর আগে bind ভাষায় ছিল না; Prototype.js-এর মতো লাইব্রেরি নিজেরাই এটি লিখে দিত, আর পরে পুরোনো ব্রাউজারের জন্য মানুষ ঠিক এমন একটি polyfill ব্যবহার করত। নিচের সংস্করণটি built-in prototype বদলায় না (পর্ব ১৪-এর পরামর্শ মনে রাখুন) — এটি একটি সাধারণ function:
function myBind(fn, thisArg, ...preset) {
return function bound(...later) {
const args = [...preset, ...later]; // আগে বসানো + পরে দেওয়া argument
if (new.target) return new fn(...args); // new দিয়ে ডাকলে thisArg উপেক্ষা
return fn.apply(thisArg, args); // নইলে this = thisArg
};
}
function greet(greeting, mark) { return `${greeting}, ${this.name}${mark}`; }
const hi = myBind(greet, { name: "Riya" }, "আসসালামু আলাইকুম");
hi("!"); // "আসসালামু আলাইকুম, Riya!"
hi.call({ name: "Nila" }, "!"); // এখনো "…Riya!" — call হেরে যায়তিনটি জিনিস লক্ষ করুন। (১) Closure: bound function পরে যেকোনো জায়গা থেকে ডাকা হলেও fn, thisArg আর preset মনে রাখে — পর্ব ২২-এর closure. এজন্যই bind-এর this “স্থায়ী”: সেটি আর ডাকার ধরন থেকে আসে না, closure-এর একটি সাধারণ variable থেকে আসে। (২) কেন call হারে: hi.call(...) যে this দেয়, bound তার দিকে তাকায়ই না — সে fn.apply(thisArg, …) চালায়। দ্বিতীয় bind কেন অকেজো, তাও একই কারণে। (৩) new.target: new দিয়ে ডাকা হলে এটি একটি মান পায়, তখন আমরা আসল function-কে new দিয়ে ডাকি — তাই new binding bind-কে হারায়। bound-কে arrow দিয়ে লিখলে এটি কাজ করত না, কারণ arrow-কে new দিয়ে ডাকাই যায় না।
আসল bind এর চেয়ে নিখুঁত: সে name-এ “bound ” বসায়, length ঠিক করে, bound function-এর নিজের prototype property রাখে না, আর instanceof সঠিকভাবে আসল function-এর দিকে পাঠায়। তবু মূল ভাবনাটি এই কয়েক লাইনেই — bind = closure-এ this ধরে রাখা + apply দিয়ে ডাকা. আর arrow function-কে একইভাবে ভাবা যায়: সে যেন জন্মের সময়ই চারপাশের this-কে closure-এ ধরে রাখে।
৯ · AI-এর যুগে — AI-এর লেখা this-কোড যাচাই করা
this-এর ভুল সাধারণত কোড লেখার সময় দেখা যায় না; দেখা যায় যখন কোনো বোতাম চাপা হয় বা timer চলে — অর্থাৎ পরে, অন্য জায়গায়। AI টুলও এখানে নিয়মিত ভুল করে, কারণ সে লাখো কোডের নমুনা থেকে শেখে, যেখানে React class component-এর .bind(this), jQuery-এর this আর আধুনিক arrow — সব মিশে আছে।
| AI-এর ভুল | কেন সমস্যা | কী চাইবেন |
|---|---|---|
Object literal-এর method arrow দিয়ে লেখা (greet: () => this.name) | this object নয়, বাইরের স্তরের | method shorthand greet() { … } |
Method সরাসরি callback হিসেবে পাঠানো (setTimeout(this.save, 500), btn.onclick = cart.add) | ডাকার সময় this হারায় | arrow wrapper বা একবার বানানো bound function |
সবখানে .bind(this) ছিটানো, এমনকি arrow-এর ওপরও | অকারণ নতুন function; arrow-এর ওপর bind অকেজো; কোড ভারী | যেখানে দরকার শুধু সেখানে; arrow-এ কখনো নয় |
addEventListener-এ .bind(this), পরে removeEventListener-এ আবার .bind(this) | দুটি আলাদা function — listener সরে না, memory leak | bound function একবার রেখে দুই জায়গায় একই reference |
Event handler-এ this ধরে নেওয়া component/object | সাধারণ function handler-এ this = element | arrow, বা element দরকার হলে e.currentTarget |
Constructor function new ছাড়া ডাকা, sloppy mode-এ | global object-এ property লেখা হয়, চুপচাপ | class ব্যবহার — new ছাড়া ডাকলে TypeError |
const self = this / var that = this নতুন কোডে | পুরোনো ধরন; arrow থাকতে অপ্রয়োজনীয় | arrow callback |
AI-কে এমন কোড লেখাতে চাইলে this-এর নিয়মগুলো আগেই বলে দিন, আর প্রতিটি this-এর ব্যাখ্যাও চেয়ে নিন:
AI-এর লেখা this-কোড হাতে পেলে — যাচাইয়ের চেকলিস্ট
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন।
প্র ০১C++ আর Java-তে this class দিয়ে স্থির, অথচ JavaScript ডাকার মুহূর্তে this ঠিক করে। ১৯৯৫ সালে এই “dynamic this”-এর নকশা কেন বেছে নেওয়া হলো?
উত্তরটি আসে JavaScript-এর দুটি মূল সিদ্ধান্ত থেকে, যেগুলো Brendan Eich ১৯৯৫ সালে Netscape-এ খুব অল্প সময়ে ভাষাটির প্রথম সংস্করণ তৈরির সময় নেন। প্রথমত, function হবে first-class মান — এই ধারণা এসেছে Scheme থেকে। দ্বিতীয়ত, class থাকবে না; object অন্য object থেকে উত্তরাধিকার পাবে prototype-এর মাধ্যমে — এই ধারণা এসেছে Self থেকে (পর্ব ১৪-এর ভাবনার প্রশ্ন ১)।
এই দুটি সিদ্ধান্ত একসাথে নিলে “class দিয়ে স্থির this” আর সম্ভব থাকে না। Java-তে method একটি class-এর অংশ; compiler জানে method-টি কোন ধরনের object-এর জন্য, তাই this লুকানো একটি parameter হিসেবে compiler নিজেই বসায়। JavaScript-এ method মানে শুধু একটি property, যার মান একটি function. সেই function একই সময়ে দশটি object-এর property হতে পারে। বিশেষ করে prototype-এর ক্ষেত্রে: একটি Shop.prototype.label function সব দোকান-object ভাগ করে নেয় — মেমরিতে একটিই কপি। এই একটি function যেন প্রতিটি দোকানের নিজের ডেটায় কাজ করে, তার একমাত্র উপায় হলো ডাকার মুহূর্তে বলে দেওয়া “এবার কোন দোকান” — অর্থাৎ shopA.label()-এ ডটের বাঁ পাশ থেকে this নেওয়া।
Self-এও একই সমাধান ছিল: সেখানে একটি message পাঠানো হয় একটি “receiver” object-কে, আর method খুঁজে পাওয়া যাক parent-এ, তবুও self থাকে মূল receiver. JavaScript এই receiver-এর ধারণাই নিয়েছে, কিন্তু function-কে স্বাধীন মান বানানোর ফলে একটি নতুন প্রশ্ন তৈরি হলো: function-কে receiver ছাড়াই ডাকলে কী হবে? ১৯৯৫ সালের উত্তর ছিল সবচেয়ে সহজটি — global object. আর “Java-র মতো দেখাতে হবে” বাজারজাত চাপের কারণে new আর constructor function যোগ হলো, যা একটি চতুর্থ নিয়ম তৈরি করল।
এর লাভ বাস্তব: একই function বহু object-এ ভাগ করা যায়, method ধার করা যায় (Array.prototype.slice.call), আর mixin-এর মতো কৌশল সহজ হয়। দাম হলো হারানো this: function যেহেতু স্বাধীন, তাকে object থেকে আলাদা করা খুব সহজ, আর আলাদা হলেই receiver হারায়। পরের প্রায় বিশ বছর ধরে ভাষাটি এই দামের সাথে মানিয়ে নেওয়ার উপায় যোগ করেছে — bind (ES5), strict mode-এর undefined (ES5), আর arrow function (ES2015).
মূল উপলব্ধি: Dynamic this কোনো খামখেয়ালি নকশা নয়; এটি “function স্বাধীন মান” আর “class নেই, prototype ভাগাভাগি” — এই দুই সিদ্ধান্তের যৌক্তিক ফল। সেই দুটি বুঝলে this-এর প্রতিটি নিয়ম স্বাভাবিক মনে হয়।
প্র ০২পুরোনো JavaScript কোডে var self = this; বা var that = this; সর্বত্র দেখা যায়। এই অভ্যাস কেন তৈরি হলো, আর ES2015-এর arrow function কীভাবে এটিকে প্রায় বিলুপ্ত করল?
২০০০-এর দশকে Ajax আর jQuery-এর যুগে JavaScript কোড ভরে উঠল callback-এ: event handler, timer, Ajax-এর সাফল্যের callback. একটি সাধারণ দৃশ্য: একটি object-এর method সার্ভার থেকে ডেটা আনে, আর ডেটা এলে callback-এ সেই object-এর একটি property আপডেট করতে হবে। কিন্তু callback ছিল সাধারণ function, আর তাকে ডাকত ব্রাউজার বা লাইব্রেরি — তাই ভেতরের this হয়ে যেত window বা অন্য কিছু। বাইরের method-এর this ভেতরে পৌঁছাত না।
সমাধানটি এল closure থেকে: this একটি variable নয়, কিন্তু তার মান একটি variable-এ রেখে দেওয়া যায় — var self = this; — আর ভেতরের function closure দিয়ে self দেখতে পায় (পর্ব ২২)। Douglas Crockford ২০০৮ সালের বিখ্যাত বই “JavaScript: The Good Parts”-এ that নামটি জনপ্রিয় করেন, আর this-এর এই আচরণকে ভাষার নকশার একটি ভুল হিসেবে বর্ণনা করেন। নানা কোডবেসে self, that, _this, me — নানা নাম চলত।
একই সময়ে লাইব্রেরিগুলো আরেকটি পথ দিল: Prototype.js একটি bind method যোগ করল, আর পরে ES5 (২০০৯) Function.prototype.bind ভাষার অংশ করল। কিন্তু প্রতিটি callback-এর শেষে .bind(this) লেখা বিরক্তিকর আর ভুলে যাওয়া সহজ।
আসল পরিবর্তনের অনুপ্রেরণা এল CoffeeScript (২০০৯) নামের একটি ভাষা থেকে, যা JavaScript-এ রূপান্তরিত হয়ে চলে। CoffeeScript-এ দুই রকম function ছিল: -> সাধারণ, আর “fat arrow” =>, যা বাইরের this বেঁধে রাখে। এটি এত জনপ্রিয় হয় যে TC39 ES2015-এ arrow function আনে — আর মানদণ্ডে সিদ্ধান্তটি আরও পরিষ্কার: arrow-এর নিজের this একেবারেই নেই, সে বাইরের scope থেকে নেয়, ঠিক যেমন self variable closure দিয়ে আসত। TypeScript আর Babel ES2015-এর আগেই arrow-কে পুরোনো JavaScript-এ রূপান্তর করত — আর রূপান্তরিত কোডে কী দেখা যেত? var _this = this; — অর্থাৎ arrow আসলে সেই পুরোনো কৌশলেরই একটি ভাষাগত রূপ।
মূল উপলব্ধি: প্রোগ্রামাররা বছরের পর বছর হাতে যে নকশা (pattern) লেখেন, ভালো ভাষা শেষ পর্যন্ত তাকেই নিজের বৈশিষ্ট্য বানিয়ে নেয়। self = this-এর ইতিহাস জানলে বোঝা যায় arrow কেন তৈরি হয়েছিল — আর কেন সেটি method হিসেবে নয়, callback হিসেবেই সবচেয়ে ভালো।
প্র ০৩ES5 (২০০৯)-এর strict mode খালি ডাকে this-কে global object থেকে undefined বানাল। এটি শুধু সুবিধার জন্য, নাকি এর পেছনে নিরাপত্তার কারণও ছিল?
দুটোই। সুবিধার দিকটি অংশ ৩-এ দেখেছেন: new ছাড়া constructor ডাকলে বা method আলাদা করে ডাকলে sloppy mode-এ this.x = … চুপচাপ global variable বানায় — যা অনেক দূরের কোনো কোডকে ভেঙে দিতে পারে, আর খুঁজে পাওয়া কঠিন। undefined হলে প্রথম ব্যবহারেই TypeError, ভুলটি জন্মের জায়গাতেই ধরা পড়ে।
কিন্তু ES5-এর নকশায় নিরাপত্তার একটি বড় চাহিদা ছিল। ২০০০-এর দশকের শেষে ওয়েবে “অন্যের কোড” চালানো সাধারণ হয়ে উঠছিল — বিজ্ঞাপন, widget, মাশআপ। প্রশ্ন ছিল: একটি পাতায় বাইরের কোড চালাতে দিয়ে কীভাবে নিশ্চিত হওয়া যায় যে সে পাতার সবকিছু ছুঁতে পারবে না? Google-এর Caja প্রকল্প আর Crockford-এর ADsafe এই সমস্যা নিয়ে কাজ করছিল, আর Caja-র প্রধান গবেষক Mark S. Miller TC39-এ যোগ দিয়ে ES5-এর strict mode-এর নকশায় বড় ভূমিকা রাখেন। তাঁদের লক্ষ্য ছিল JavaScript-কে এমন করা, যাতে একটি function কেবল সেই জিনিসগুলো ছুঁতে পারে, যা তাকে স্পষ্টভাবে দেওয়া হয়েছে (object-capability ভাবনা)।
Sloppy mode-এ এর পথে বড় বাধা ছিল this. যেকোনো function-কে খালি ডাকলে, বা call(null) দিয়ে ডাকলে, সে this দিয়ে global object পেয়ে যেত — আর global object মানে পাতার সবকিছু: document, cookie, network. অর্থাৎ যে কোড-কে আপনি একটি সাধারণ callback হিসেবে চালাচ্ছেন, সে চাইলেই পুরো পাতার চাবি পেত। Strict mode এই ফাঁক বন্ধ করে: this হয় হুবহু কলকারী যা দেয়, নইলে undefined. একই কারণে strict mode-এ primitive this-কে আর object-এ মোড়ানো হয় না, আর arguments.callee ও caller-এর মতো ফাঁকও বন্ধ করা হয়।
পুরোনো পাতা না ভাঙতে strict mode রাখা হলো ঐচ্ছিক — "use strict" একটি সাধারণ string, তাই পুরোনো ব্রাউজার এটিকে উপেক্ষা করে। পরে ES2015 থেকে module আর class-এর কোড স্বয়ংক্রিয়ভাবে strict করা হলো, যাতে নতুন কোড ডিফল্টে নিরাপদ নিয়মে চলে।
মূল উপলব্ধি: “খালি ডাকে this = undefined” ছোট একটি নিয়ম মনে হলেও এটি একটি নিরাপত্তা-নকশা: কোনো function যেন অজান্তে global ক্ষমতা না পায়। ভাষার ছোট ডিফল্টও কখনো কখনো বড় নিরাপত্তার প্রশ্নের উত্তর।
প্র ০৪React-এর class component-এর যুগে constructor-এ this.handleClick = this.handleClick.bind(this) লেখা একটি আচারে পরিণত হয়েছিল। এর কারণ কী, আর ২০১৯ সালের Hooks এই সমস্যাকে কীভাবে সরিয়ে দিল?
React-এর প্রথম দিকে component বানানো হতো React.createClass({ … }) দিয়ে। এই function একটি সুবিধা দিত: component-এর সব method সে নিজে থেকে component object-এর সাথে bind করে দিত (autobinding). তাই onClick={this.handleClick} লিখলেও ভেতরে this ঠিক থাকত।
ES2015 class আসার পর React ২০১৫ সালের দিকে (সংস্করণ 0.13) class Counter extends React.Component লেখার সুযোগ দেয়, আর সিদ্ধান্ত নেয় যে ভাষার সাধারণ নিয়মই মানা হবে — কোনো autobinding নয়। ফল: onClick={this.handleClick} লিখলে React পরে method-টিকে খালি ডাকে, আর class সবসময় strict, তাই this = undefined — this.setState ডাকতে গিয়ে TypeError. এই পর্বের ভাষায়: method callback হিসেবে চলে গেছে, this হারিয়েছে।
সমাধান হিসেবে তিনটি ধরন প্রচলিত হয়। (১) Constructor-এ প্রতিটি handler bind করা — this.handleClick = this.handleClick.bind(this); এটি লাখো টিউটোরিয়ালে আচারের মতো ছড়িয়ে পড়ে, অনেকে না বুঝেই লিখতেন। (২) JSX-এ arrow — onClick={() => this.handleClick()}; সহজ, কিন্তু প্রতিবার render-এ নতুন function তৈরি হয়। (৩) Class field arrow — handleClick = () => { … }; Babel-এর মাধ্যমে মানদণ্ডে আসার (ES2022) অনেক আগেই এটি জনপ্রিয় হয়।
২০১৯ সালের ফেব্রুয়ারিতে React 16.8 আনে Hooks — useState, useEffect — যা দিয়ে সাধারণ function component-এই state রাখা যায়। Hooks-এর প্রস্তাবনায় React দল সরাসরি লিখেছিল যে class মানুষ আর মেশিন দুজনকেই বিভ্রান্ত করে, আর class বুঝতে JavaScript-এ this কীভাবে কাজ করে তা বুঝতে হয়, যা অনেক ভাষার থেকে আলাদা। Function component-এ কোনো this নেই: state আসে closure দিয়ে, handler হলো component function-এর ভেতরের একটি সাধারণ arrow. হারানোর মতো কোনো this-ই আর নেই।
মূল উপলব্ধি: একটি জনপ্রিয় framework-এর পুরো নকশা বদলের পেছনে আংশিকভাবে ছিল this-এর জটিলতা। কখনো সমস্যার সবচেয়ে ভালো সমাধান হলো সমস্যাটিকেই নকশা থেকে সরিয়ে দেওয়া — this-এর বদলে closure.
প্র ০৫Python প্রতিটি method-এ self স্পষ্টভাবে লিখতে বাধ্য করে; JavaScript-এ this লুকানো আর ডাকার সময় ঠিক হয়। দুই নকশার লাভ-ক্ষতি কী?
Python-এ method লিখতে হয় def total(self): — প্রথম parameter-টিই object. এটি নিয়ে অভিযোগ বহু পুরোনো: “প্রতিবার self লিখতে হয় কেন?” ২০০৮ সালে Guido van Rossum “Why explicit self has to stay” নামে একটি ব্লগ লেখেন, যেখানে তিনি ব্যাখ্যা করেন কেন self স্পষ্ট রাখা হবে। তাঁর যুক্তিগুলোর একটি: method আসলে একটি সাধারণ function, আর obj.total() হলো Cart.total(obj)-এর একটি সুবিধাজনক রূপ — কোনো জাদু নেই।
কিন্তু Python-এর আসল চতুর অংশ অন্য জায়গায়। obj.total লিখলেই (ডাকার আগেই) Python একটি bound method object বানায়, যা function আর obj — দুটোই মনে রাখে। তাই f = cart.total; f() Python-এ দিব্যি কাজ করে, আর button.on_click(cart.total)-এর মতো callback-এও self হারায় না। অর্থাৎ Python-এ “হারানো self” সমস্যাটি প্রায় নেই। JavaScript-এ obj.total লিখলে শুধু function-টি পাওয়া যায় — বন্ধন তৈরি হয় কেবল obj.total() ডাকার মুহূর্তে, তারপর হারিয়ে যায়।
তাহলে JavaScript-এর নকশার লাভ কী? নমনীয়তা। একই function যেকোনো object-এ ধার দেওয়া যায় (call), ডেটা আর আচরণ আলাদা রেখে পরে জোড়া যায়, আর obj.method পড়ায় কোনো নতুন object তৈরির খরচ নেই। Python-এ অন্য object-এর ওপর method চালাতে Cart.total(other) লেখা যায়, তবে তা কম প্রচলিত। C++ আর Java আরেক প্রান্তে: this লুকানো এবং class দিয়ে স্থির — হারায় না, ধারও দেওয়া যায় না; callback-এ পাঠাতে lambda বা method reference লাগে, যা ভেতরে নিজেই object ধরে রাখে।
লক্ষ করুন, তিন জগৎ শেষে একই জায়গায় মিলেছে: callback-এ object ধরে রাখতে Python-এর bound method, Java-র this::save method reference, আর JavaScript-এর bind বা arrow — সবই একটি function আর একটি object-কে একসাথে বেঁধে রাখার উপায়।
মূল উপলব্ধি: স্পষ্ট self একটু বেশি লেখায় কিন্তু কম বিস্ময় দেয়; লুকানো আর dynamic this কম লেখায় আর বেশি নমনীয়, কিন্তু নিয়ম না জানলে চমকে দেয়। ভাষা বেছে নেওয়া মানে কোন ধরনের ভুল আপনি সহজে করতে চান, তা বেছে নেওয়া।
অনুশীলন
আগে চার প্রশ্নের ছক দিয়ে উত্তর অনুমান করুন, তারপর console-এ চালিয়ে মিলিয়ে নিন।
-
আউটপুট অনুমান করুন:
function who() { "use strict"; return this?.id; }আরconst x = { id: 1, who }, y = { id: 2 };— এখনx.who(),who(),x.who.call(y),(0, x.who)()আরwho.bind(y).call(x)কী কী দেবে?1(implicit),undefined(default, strict),2(explicit জেতে implicit-এর ওপর),undefined, আর2(bind জেতে call-এর ওপর)। চতুর্থটি একটি ফাঁদ: comma operator(0, x.who)আগে function-টির মান বের করে, তারপর সেই মান ডাকা হয় — ডাকার মুহূর্তে আরx.নেই, ঠিকconst f = x.who; f()-এর মতো। Bundler-এর তৈরি কোডে এই ধরন প্রায়ই দেখা যায়, ঠিক এই কারণেই — ইচ্ছা করেthisসরাতে। -
বাগ খুঁজুন: একজন লিখেছেন
const timer = { sec: 0, start() { setInterval(function () { this.sec++; console.log(this.sec); }, 1000); } }; timer.start();— ব্রাউজারে প্রতি সেকেন্ডেNaNছাপা হচ্ছে। কেন? দুটি আলাদা সমাধান লিখুন।setInterval-কে দেওয়া সাধারণ function-কে ব্রাউজার ডাকেthis=windowদিয়ে।window.secনেই (undefined),undefined + 1=NaN, আর সেটিwindow.sec-এ লেখা হয় — প্রতিবারNaN. বাইরেরstart-এরthis(=timer) ভেতরে পৌঁছায়নি। সমাধান ১: callback arrow করুন —setInterval(() => { this.sec++; … }, 1000). সমাধান ২:setInterval(function () { … }.bind(this), 1000). (পুরোনো ধরন:const self = this;আর ভেতরেself.sec++.) -
Arrow কোথায় ঠিক, কোথায় ভুল:
const store = { name: "Nila Store", a: () => this.name, b() { return this.name; }, c() { return [1].map(() => this.name); }, d() { return [1].map(function () { return this?.name; }); } };— একটি ES module-এstore.a(),store.b(),store.c(),store.d()কী দেবে?store.a()— TypeError, কারণaarrow, তারthismodule-এর বাইরের স্তরেরthis=undefined, আরundefined.nameপড়া যায় না।store.b()—"Nila Store"(implicit).store.c()—[ 'Nila Store' ]: arrow callbackc-এরthisধার করেছে।store.d()—[ undefined ]: module strict, আরmapসাধারণ callback-কেthisছাড়া ডাকে। শিক্ষা: arrow method হিসেবে ভুল, method-এর ভেতরের callback হিসেবে ঠিক। -
ছোট কাজ:
const prices = [420, 140, 95];থেকে তিনভাবে সবচেয়ে বড় দাম বের করুন:applyদিয়ে, spread দিয়ে, আরreduceদিয়ে। তারপরbindদিয়ে একটিwarnfunction বানান, যাconsole.log-কে সবসময়"[স্টক]"উপসর্গ দিয়ে ডাকবে, আরwarn("ডাল প্রায় শেষ")চালিয়ে দেখুন।Math.max.apply(null, prices),Math.max(...prices)আরprices.reduce((m, p) => (p > m ? p : m))— তিনটিই420. প্রথম দুটি array-কে আলাদা argument-এ ভাঙে, তাই খুব বড় array-এ argument-সীমায় পড়তে পারে;reduce-এর সেই সমস্যা নেই। Logger:const warn = console.log.bind(console, "[স্টক]");—warn("ডাল প্রায় শেষ")ছাপে[স্টক] ডাল প্রায় শেষ.thisহিসেবেconsoleদেওয়া নিরাপদ অভ্যাস, কারণ কিছু পুরোনো environment-এconsole.logনিজেরthisহিসেবেconsoleআশা করত।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমJavaScript-এ একটি function ডাকার লাইনে this বের করতে কোন ক্রমে প্রশ্ন করবেন, সাজান (▲▼ চেপে বা টেনে)।
- 🏹 Function-টি কি arrow? হলে চারপাশের this
- 🏗️ new দিয়ে ডাকা? হলে নতুন object
- 🎯 call, apply বা bind দিয়ে object দেওয়া? হলে সেই object
- 🔹 obj.method() আকারে ডাকা? হলে ডটের বাঁ পাশের object
- 🕳️ কিছুই না: strict-এ undefined, নইলে global object
JavaScript-এ function f() { "use strict"; return this; } লেখা থাকলে খালি f() ডাকলে কী ফেরত আসে?
- window বা global object
- function f নিজে
- undefined
- ReferenceError
function g() { return this.n; } আর const h = g.bind({ n: 1 }); — JavaScript-এ h.call({ n: 2 }) কত দেয়?
- 1
- 2
- undefined
- TypeError
JavaScript-এ object literal-এর ভেতরে method হিসেবে arrow function লিখলে ({ name: "A", f: () => this.name }) arrow-এর ভেতরে this কী?
- object-টি নিজে
- একটি নতুন খালি object
- যে f ডাকে, তার ডটের বাঁ পাশের object
- object literal-এর বাইরের কোডের this
JavaScript-এ call আর apply-এর মূল পার্থক্য কী?
- call এখনই চালায়, apply নতুন function ফেরত দেয়
- call argument একটি একটি করে নেয়, apply একটি array হিসেবে
- call শুধু arrow function-এ চলে
- apply শুধু strict mode-এ চলে
ব্রাউজারে button.addEventListener("click", function () { console.log(this); }) — বোতামে ক্লিক করলে this কী?
- window
- undefined
- button element
- click event object
function Shop(n) { this.n = n; }, const B = Shop.bind({ n: "X" }); — JavaScript-এ new B("Y").n কী দেয়?
- "X"
- TypeError
- undefined
- "Y"
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখতে পাবেন।
মনে রাখুন
- সাধারণ function-এর
thisঠিক হয় ডাকার ধরন দেখে, লেখার জায়গা দেখে নয়। প্রশ্ন সবসময়: “এই লাইনে কীভাবে ডাকা হলো?” - অগ্রাধিকার: new > call/apply/bind > obj.method() > default. Default = strict-এ
undefined, sloppy script-এ global object; module আর class সবসময় strict. - Arrow-এর নিজের
thisনেই — চারপাশ থেকে ধার করে,call/bind-এ বদলায় না,new-এ চলে না। Method-এর ভেতরের callback-এ arrow; method নিজে shorthand. - Method object ছেড়ে গেলে (variable, timer, array method, event)
thisহারায়। সমাধান: arrow wrapper,bind,thisArg, class-এর arrow field. call= comma,apply= array, দুটোই এখনই চালায়;bindনতুন function দেয়, প্রথম bind-ই স্থায়ী। bind = closure-এthisধরে রাখা +apply.
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ২৫ · prototype আর prototype chainপরবর্তী পর্বMethod কীভাবে ভাগাভাগি হয়, আর
thisসেখানে কী কাজ করে। - JavaScript প্রোগ্রামিং · কোর্স হোম৫৯টি পর্বপুরো কোর্সের রোডম্যাপ — কোন পর্ব কখন।
- Java-র constructor আর thisতুলনা করুনClass দিয়ে স্থির
this— যেখানেthisকখনো হারায় না। - Python-এর class, object আর selfতুলনা করুনস্পষ্ট
selfআর bound method — অন্য পথে একই সমস্যার সমাধান।