সরাসরি পর্বে যান
পর্ব ২৪ · ৫৯-এর মধ্যে · মডিউল ৪

this, call, apply আর bind — this কে ঠিক করে

JavaScript this — the Four Binding Rules, Arrow Functions, Lost this, call / apply / bind

Taught in Bangla, this lesson explains how JavaScript decides this: the four binding rules and their priority, default binding in strict and sloppy mode, implicit binding and the lost-this problem, arrow functions borrowing this from their surroundings, setting this with call, apply and bind, a full map of this in different places, and writing bind yourself.

এই পর্বে যা শিখবেন

  • 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/C++/Python/Java জানলে

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সদ্য তৈরি নতুন objectnew Shop("Rahim")
২call, apply বা bind দিয়ে object দেওয়া হয়েছে?explicit bindingযে object দেওয়া হলোgreet.call(nila)
৩obj.method() আকারে, ডট বা bracket দিয়ে ডাকা?implicit bindingডটের বাঁ পাশের objectriya.greet()
৪ওপরের কোনোটিই নাdefault bindingstrict mode-এ undefined; সাধারণ script-এ global objectgreet()

আর এই চার প্রশ্নের আগে একটি “শূন্যতম” প্রশ্ন আছে: function-টি কি arrow? হলে চারটি নিয়মের কোনোটিই খাটে না — arrow-এর this সবসময় চারপাশের কোডের this (অংশ ৫)। এখন সিদ্ধান্তের ধাপগুলো চারটি বাস্তব ডাকের ওপর চালিয়ে দেখুন:

  1. ১. new দিয়ে?new Shop(…)হ্যাঁ → নতুন object
  2. ২. call/apply/bind?f.call(obj)হ্যাঁ → দেওয়া object
  3. ৩. obj.method()?riya.greet()হ্যাঁ → ডটের বাঁ পাশ
  4. ৪. defaultgreet()strict → undefined
প্রস্তুতিএকটি function, দুটি object
function greet() { "use strict"; return this; }
function Shop(name) { this.name = name; }
const riya = { name: "Riya", greet };
const nila = { name: "Nila" };
ডাক ১new Shop("Rahim Store")
প্রশ্ন ১: new আছে? হ্যাঁ
→ engine একটি খালি object বানাল, this = সেই object
→ this.name = "Rahim Store"; object-টিই ফেরত
ডাক ২riya.greet.call(nila)
প্রশ্ন ১: new? না
প্রশ্ন ২: call দিয়ে object দেওয়া? হ্যাঁ — nila
→ ডটের বাঁ পাশে riya থাকলেও this = nila
ডাক ৩riya.greet()
প্রশ্ন ১: না · প্রশ্ন ২: না
প্রশ্ন ৩: obj.method() আকার? হ্যাঁ — ডটের বাঁ পাশে riya
→ this = riya
ডাক ৪greet()
প্রশ্ন ১, ২, ৩: সব না
→ default binding: function-এ "use strict" আছে
→ this = undefined  // strict না হলে global object
  1. একই function, চারটি ডাক। greet শুধু নিজের this ফেরত দেয়। আমরা চারভাবে ডাকব আর প্রতিবার ছকের প্রশ্নগুলো ওপর থেকে নিচে করব। মনে রাখুন, প্রশ্নের ক্রমই অগ্রাধিকার।
  2. new — সবার আগে। new দেখলেই engine একটি নতুন খালি object বানায়, this-কে সেটির সাথে বাঁধে, function চালায়, আর (function অন্য কোনো object ফেরত না দিলে) সেই object ফেরত দেয়। new আর constructor-এর পুরো গল্প পর্ব ২৬-এ।
  3. Explicit — হাতে দেওয়া object. call বলে দেয় “এবার this হবে nila”. লক্ষ করুন, ডটের বাঁ পাশে riya আছে, তবুও explicit binding implicit-কে হারায় — কারণ প্রশ্ন ২ আগে আসে।
  4. Implicit — ডটের বাঁ পাশ। কোনো new নেই, কোনো call নেই, কিন্তু ডাকটি riya.greet() আকারে। তাই this = riya. সবচেয়ে প্রচলিত নিয়ম, আর পর্ব ১৪-এ এটিই শিখেছিলেন।
  5. 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());
নাম, function আর this
cart
f
g
show-এর this
  1. Method আসলে একটি আলাদা function. show object-এর “ভেতরে” থাকে না; object-এর show property-তে থাকে শুধু একটি function-এর reference — ধরুন তার নাম F. পর্ব ১৪-এর reference-এর ধারণা এখানেও খাটে।
  2. cart.show() — implicit. ডাকার মুহূর্তে ডটের বাঁ পাশে cart, তাই this = cart, ফল Riya.
  3. const f = cart.show — শুধু function কপি। এখানে কোনো ডাক নেই (বন্ধনী নেই)। f-এ গেল F-এর reference — object-এর সাথে কোনো সম্পর্ক নয়। f === cart.show সত্য।
  4. f() — default! ডাকার মুহূর্তে ডটের বাঁ পাশে কেউ নেই, তাই চতুর্থ নিয়ম। show strict, তাই this = undefined. this?.owner তাই undefined দিল। ?. না থাকলে এখানে TypeError হতো।
  5. bind — নতুন, বাঁধা function. cart.show.bind(cart) F-কে বদলায় না; একটি নতুন bound function তৈরি করে, যার ভেতরে লেখা থাকে “ডাকলে F চালাবে, this = cart দিয়ে”.
  6. 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 হয় একটি Timeout object. দুই ক্ষেত্রেই cart নয়।
  • Array method: orders.forEach(printer.print). forEach callback-কে ডাকে 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 wrappersetTimeout(() => cart.report(), 1000)সবচেয়ে পরিষ্কার; ডাকটি আবার obj.method() আকারে হয়
bindsetTimeout(cart.report.bind(cart), 1000)একই বাঁধা function বারবার লাগলে, বা পরে removeEventListener-এ একই reference দরকার হলে
thisArgorders.forEach(printer.print, printer)forEach, map, filter, some, every, find — সবাই দ্বিতীয় argument হিসেবে this নেয়
Class-এর arrow fieldreport = () => { … }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();
দুই স্তরের this
বাইরের method-এর this
ভেতরের callback-এর this
ফল
  1. team.withArrow(). Implicit binding — withArrow-এর ভেতরে this = team. এ পর্যন্ত নতুন কিছু নেই।
  2. Arrow callback-এর this. map arrow-টিকে ডাকে। কিন্তু arrow-এর নিজের this নেই, তাই সে বাইরে তাকায় — যে method-এর ভেতরে লেখা, সেই withArrow-এর this, অর্থাৎ team. map কীভাবে ডাকল, তা arrow-এর কাছে অপ্রাসঙ্গিক। ফল [ 'Riya · Tigers', 'Nila · Tigers' ].
  3. team.withFunction(). আবার implicit binding, this = team. বাইরের স্তরে সব ঠিক।
  4. সাধারণ function callback — হারাল! ভেতরের function (p)-এর নিজস্ব this আছে, আর সেটি ঠিক করে যে ডাকে — map. map তাকে খালি ডাকে (thisArg ছাড়া), তাই default binding: strict-এ undefined. বাইরের method-এর this যে team, তাতে কিছু যায় আসে না। ফল [ undefined, undefined ].
  5. তিনটি সমাধান। আধুনিক উপায়: 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-এর prototype property-ও নেই।
  • নিজের 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 কীভাবেফল
callf.call(obj, a, b)একটি একটি করে, কমা দিয়েএখনই চালায়, ফল ফেরত দেয়
applyf.apply(obj, [a, b])একটি array (বা array-এর মতো object) হিসেবেএখনই চালায়, ফল ফেরত দেয়
bindf.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());
Bound function-এর ভেতর
fa
fab
show-এর আসল this
  1. একটি function, দুটি object. show নিজের this-এর name ফেরত দেয়। এখনো কিছু বাঁধা হয়নি।
  2. show.bind(a). নতুন bound function fa তৈরি হলো। ভেতরে দুটি তথ্য স্থায়ীভাবে লেখা: “আসল function = show” আর “this = a”. show নিজে বদলায়নি।
  3. fa(). খালি ডাক, কিন্তু fa নিজেই ভেতরে show-কে a দিয়ে ডাকে। ফল A.
  4. fa.call(b) — call হেরে গেল। call fa-কে this = b দিয়ে ডাকে ঠিকই, কিন্তু fa সেই this ব্যবহারই করে না — সে সবসময় নিজের লেখা a দিয়ে show ডাকে। ফল আবার A.
  5. fa.bind(b) — প্রথম bind-ই জেতে। দ্বিতীয় bind fa-এর ওপর আরেকটি মোড়ক বানায় (this = b), কিন্তু ভেতরের fa আবারও a দিয়েই show ডাকে। দ্বিতীয় bind শুধু বাড়তি argument যোগ করতে পারে, this বদলাতে পারে না। ফল A.
  6. 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-এর বাইরের স্তরundefinedmodule সবসময় strict
Node.js CommonJS ফাইলের বাইরের স্তরmodule.exports (শুরুতে {})Node ফাইলটিকে একটি function-এ মুড়ে চালায়
সাধারণ function, খালি ডাকstrict: undefined; sloppy: global objectdefault
obj.method()objimplicit
f.call(x) / f.apply(x) / bound functionxexplicit
new F()নতুন objectnew
Arrow functionযেখানে লেখা, সেখানকার thislexical
Class-এর method (obj.method() দিয়ে ডাকলে)সেই object; খালি ডাকলে undefinedimplicit; class সবসময় strict
Class-এর static method (Cart.create())class নিজে (Cart)implicit — ডটের বাঁ পাশে class
Class field-এর initializer (items = [])তৈরি হতে থাকা নতুন objectconstructor-এর সময় চলে
addEventListener-এ সাধারণ functionযে element-এ listener বসানো (e.currentTarget)ব্রাউজার explicit-ভাবে বসায়
addEventListener-এ arrowবাইরের thislexical
setTimeout-এ সাধারণ functionব্রাউজারে global object; Node.js-এ Timeout objecttimer যেভাবে ডাকে

একটি “পছন্দ” বোতাম — 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 → TypeError

Field 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-এর পার্থক্য? — call argument কমা দিয়ে নেয়, apply array হিসেবে; দুটোই এখনই চালায়। 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 leakbound function একবার রেখে দুই জায়গায় একই reference
Event handler-এ this ধরে নেওয়া component/objectসাধারণ function handler-এ this = elementarrow, বা 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-এর ব্যাখ্যাও চেয়ে নিন:

একটি অনলাইন দোকানের জন্য JavaScript-এ একটি cart object লিখে দাও: items, add(item), total(), আর saveLater() — যা ৫০০ মিলিসেকেন্ড পরে total console-এ দেখাবে। একটি "যোগ করুন" বোতামে click হলে add চলবে। নিয়ম: ১. object-এর method লিখবে method shorthand দিয়ে — কোনো method নিজে arrow function হবে না। ২. method-এর ভেতরের callback (setTimeout, map, forEach, addEventListener) হবে arrow function. ৩. কোনো method সরাসরি callback হিসেবে পাঠাবে না (setTimeout(cart.save) নয়); দরকার হলে arrow wrapper দেবে। ৪. .bind(this) শুধু তখনই, যখন একই function পরে removeEventListener-এ লাগবে — আর তখন bound function একবার বানিয়ে একটি property-তে রাখবে। ৫. const self = this বা var that = this ব্যবহার করবে না। ৬. কোডের প্রতিটি this-এর পাশে মন্তব্যে লিখবে: এটি কোন নিয়মে (new / explicit / implicit / default / arrow) কোন object. ৭. শেষে দেখাও: const f = cart.total; f() strict mode-এ কী হয় আর কেন।

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-এ চালিয়ে মিলিয়ে নিন।

  1. আউটপুট অনুমান করুন: 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 সরাতে।

  2. বাগ খুঁজুন: একজন লিখেছেন 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++.)

  3. 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, কারণ a arrow, তার this module-এর বাইরের স্তরের this = undefined, আর undefined.name পড়া যায় না। store.b() — "Nila Store" (implicit). store.c() — [ 'Nila Store' ]: arrow callback c-এর this ধার করেছে। store.d() — [ undefined ]: module strict, আর map সাধারণ callback-কে this ছাড়া ডাকে। শিক্ষা: arrow method হিসেবে ভুল, method-এর ভেতরের callback হিসেবে ঠিক।

  4. ছোট কাজ: const prices = [420, 140, 95]; থেকে তিনভাবে সবচেয়ে বড় দাম বের করুন: apply দিয়ে, spread দিয়ে, আর reduce দিয়ে। তারপর bind দিয়ে একটি warn function বানান, যা 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 বের করতে কোন ক্রমে প্রশ্ন করবেন, সাজান (▲▼ চেপে বা টেনে)।

  1. 🏹 Function-টি কি arrow? হলে চারপাশের this
  2. 🏗️ new দিয়ে ডাকা? হলে নতুন object
  3. 🎯 call, apply বা bind দিয়ে object দেওয়া? হলে সেই object
  4. 🔹 obj.method() আকারে ডাকা? হলে ডটের বাঁ পাশের object
  5. 🕳️ কিছুই না: 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"

শব্দকোষ

লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখতে পাবেন।

মনে রাখুন

  1. সাধারণ function-এর this ঠিক হয় ডাকার ধরন দেখে, লেখার জায়গা দেখে নয়। প্রশ্ন সবসময়: “এই লাইনে কীভাবে ডাকা হলো?”
  2. অগ্রাধিকার: new > call/apply/bind > obj.method() > default. Default = strict-এ undefined, sloppy script-এ global object; module আর class সবসময় strict.
  3. Arrow-এর নিজের this নেই — চারপাশ থেকে ধার করে, call/bind-এ বদলায় না, new-এ চলে না। Method-এর ভেতরের callback-এ arrow; method নিজে shorthand.
  4. Method object ছেড়ে গেলে (variable, timer, array method, event) this হারায়। সমাধান: arrow wrapper, bind, thisArg, class-এর arrow field.
  5. call = comma, apply = array, দুটোই এখনই চালায়; bind নতুন function দেয়, প্রথম bind-ই স্থায়ী। bind = closure-এ this ধরে রাখা + apply.

পরের পর্বে: এই পর্বে বারবার শুনেছেন “method prototype-এ থাকে আর সব object ভাগ করে নেয়”. সেই ভাগাভাগি ঠিক কীভাবে হয়? পর্ব ২৫-এ prototype আর prototype chain: __proto__, Object.create, constructor function-এর prototype property — আর this কেন prototype-এর method-কে প্রতিটি object-এর নিজের ডেটায় কাজ করতে দেয়।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

এই পর্বটি শেয়ার করুন:
এই পর্বে ভুল পেয়েছেন? জানান