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

function — declaration, arrow, default আর rest

JavaScript Functions — Declarations, Expressions, Arrow Functions, Default & Rest Parameters, Callbacks & Recursion

Taught in Bangla, this lesson explains JavaScript functions: arguments, parameters, return values and the call stack, declarations versus expressions versus arrow functions, default and rest parameters, the old arguments object and spread, functions as values with callbacks and higher-order functions, where arrow functions break this, pure functions and recursion.

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

  • Function ডাকলে ভেতরে ঠিক কী ঘটে — argument থেকে parameter, body চলা, return value আর call stack
  • Function declaration, function expression আর arrow function-এর পার্থক্য, আর কোনটি কখন
  • Default parameter কখন খাটে, rest parameter কীভাবে কাজ করে, আর arguments কেন পুরোনো
  • Function-কে মান হিসেবে ব্যবহার — callback, higher-order function, function ফেরত দেওয়া
  • Pure function, side effect, recursion আর JSDoc দিয়ে function-এর চুক্তি লেখা

১ · Function কী — নাম দেওয়া, বারবার ব্যবহারযোগ্য একটি কাজ

পর্ব ১২ পর্যন্ত আপনার কোড ছিল ওপর থেকে নিচে চলা একটি লম্বা তালিকা — মান রাখা, সিদ্ধান্ত নেওয়া, লুপ ঘোরানো। কিন্তু একটি অনলাইন দোকানের কথা ভাবুন। দামের সাথে VAT যোগ করার হিসাব লাগে কার্ট পাতায়, চেকআউটে, রসিদে, অ্যাডমিন রিপোর্টে। প্রতিটি জায়গায় একই হিসাব আলাদা করে লিখলে চারটি কপি হবে। হার বদলালে চার জায়গায় বদলাতে হবে, আর একটি ভুলে গেলেই রসিদ আর কার্টে দুই রকম দাম।

সমাধান হলো হিসাবটিকে একবার লিখে একটি নাম দেওয়া — একটি function. তারপর যেখানে দরকার, শুধু নাম ধরে ডাকা। Function-এর তিনটি অংশ মনে রাখুন:

📥 ইনপুট — parameter

Function কাজ করতে কোন কোন তথ্য চায়, তার নামের তালিকা। যেমন amount আর rate.

⚙️ কাজ — body

বাঁকা বন্ধনী { }-এর ভেতরের statement-গুলো। প্রতিবার ডাকলে নতুন করে চলে।

📤 আউটপুট — return value

return দিয়ে যে মান বাইরে পাঠানো হয়। যে জায়গা থেকে ডাকা হয়েছিল, মানটি সেখানেই বসে।

function addVat(amount, rate) {     // amount, rate — parameter
  return amount + amount * rate;    // ফল বাইরে পাঠানো
}

const price = addVat(500, 0.15);    // 500, 0.15 — argument
console.log(price);                 // 575

প্রথম তিন লাইনে function তৈরি হলো — এখনো কিছু হিসাব হয়নি, শুধু রেসিপি লেখা হলো। পঞ্চম লাইনে function ডাকা হলো (call). তখন amount পেল ৫০০, rate পেল ০.১৫, body চলল, আর return ফেরত দিল ৫৭৫. পুরো addVat(500, 0.15) লেখাটির জায়গায় যেন বসে গেল ৫৭৫ — তাই price-এর মান ৫৭৫. এখানে ১৫% শুধু উদাহরণের হার; বাংলাদেশে বাস্তব VAT হার পণ্য ও সেবাভেদে আলাদা।

পাড়ার চায়ের দোকানের কথা ভাবুন। আপনি বললেন, “দুধ চা, চিনি কম।” দোকানি প্রতিবার নতুন করে রেসিপি ভাবেন না — তাঁর মাথায় একটি নির্দিষ্ট পদ্ধতি আছে। “দুধ চা” হলো function-এর নাম, “চিনি কম” হলো argument, আর হাতে আসা কাপটি return value. একই পদ্ধতি দিনে দুইশো বার চলে, শুধু argument বদলায় — কারও চিনি বেশি, কারও আদা দিয়ে।

Parameter আর argument শব্দ দুটো প্রায়ই গুলিয়ে যায়। মনে রাখার সহজ উপায়: parameter হলো ঘরের নাম, argument হলো ঘরে রাখা জিনিস। Function লেখার সময় আপনি ঘরের নাম দেন (amount, rate); ডাকার সময় ঘরে আসল মান রাখেন (৫০০, ০.১৫)। একই function একবার ৫০০ দিয়ে, পরের বার ১২০০ দিয়ে ডাকা যায় — ঘর একই, জিনিস আলাদা।

JavaScript-এর বিশেষত্ব — function-ও একটি মান

C-তে function শুধু কোডের একটি নাম দেওয়া অংশ। JavaScript-এ function তার চেয়ে বেশি কিছু: এটি একটি মান, ঠিক number বা string-এর মতো। একে বলে first-class function. এর মানে, একটি function-কে আপনি —

  • variable-এ রাখতে পারেন: const tax = addVat;
  • অন্য function-এ argument হিসেবে পাঠাতে পারেন: checkout(cart, applyDiscount)
  • একটি function থেকে ফেরত দিতে পারেন: return (x) => x * 2;
  • array বা object-এর ভেতরে রাখতে পারেন।

এই একটি বৈশিষ্ট্য থেকেই আসে বোতামে ক্লিকের সময় কোড চালানো (event), map/filter-এর মতো array method (পর্ব ১৬), Promise (পর্ব ৩৩) আর closure (পর্ব ২২)। তাই এই পর্বটি মডিউল ২-এর শেষ ইট হলেও, পরের প্রায় সব মডিউলের ভিত্তি।

২ · Function ডাকলে কী ঘটে — argument, parameter, return আর call stack

পর্ব ০৫-এ দেখেছেন, প্রতিটি function কলে engine call stack-এ একটি নতুন stack frame বসায়। এবার একটি কলকে খুব ধীরে, ধাপে ধাপে দেখুন। “পরে” চাপুন।

1function addVat(amount, rate) {2  const vat = amount * rate;3  return amount + vat;4}5const price = addVat(500, 0.15);6console.log(price);
Call stack আর মান
stack
amount
rate
vat
price
  1. Argument হিসাব করা। লাইন ৫-এ engine আগে বন্ধনীর ভেতরের argument-গুলোর মান বের করে — এখানে সরাসরি 500 আর 0.15. Argument-এ 2 * 250 লেখা থাকলেও আগে সেটি হিসাব হয়ে ৫০০ হতো, তারপর function-এ যেত।
  2. নতুন frame, parameter-এ মান বসা। addVat-এর জন্য একটি নতুন frame call stack-এর ওপরে বসল। প্রথম argument গেল প্রথম parameter-এ (amount = 500), দ্বিতীয়টি দ্বিতীয়তে (rate = 0.15) — নাম দেখে নয়, অবস্থান দেখে। Body-র local variable vat-এর ঘরও তৈরি, কিন্তু এখনো মান নেই।
  3. Body চলছে। লাইন ২-এ vat = 500 * 0.15 = 75. এই vat শুধু এই frame-এর ভেতরে আছে — বাইরের কোড একে দেখতে পায় না।
  4. return — মান নিয়ে বেরিয়ে যাওয়া। লাইন ৩-এ 500 + 75 = 575 হিসাব হলো, আর return সেই মান নিয়ে function থেকে সাথে সাথে বেরিয়ে গেল। return-এর পরে কোনো লাইন থাকলে সেটি আর চলত না।
  5. Frame সরে গেল, কলের জায়গায় মান। addVat-এর frame pop হলো — amount, rate, vat সব হারিয়ে গেল। লাইন ৫-এর addVat(500, 0.15) লেখাটির জায়গায় এখন যেন 575; তাই price = 575.
  6. ফল ছাপা। লাইন ৬-এ console.log (সেটিও একটি function) ছাপল 575. Stack-এ আবার শুধু global.
Argument হিসাব → নতুন frame → parameter-এ মান → body → return → frame pop → কলের জায়গায় ফল

কম বা বেশি argument দিলে — JavaScript চুপ থাকে

Java বা C-তে parameter দুটি থাকলে ঠিক দুটি argument দিতেই হয়, নইলে compile হয় না। JavaScript এ ব্যাপারে খুব নরম:

  • কম দিলে: যে parameter মান পায়নি, তার মান হয় undefined. addVat(500) চালালে rate হয় undefined, আর 500 * undefined হয় NaN — কোনো error ছাড়াই ফল আসে NaN.
  • বেশি দিলে: বাড়তি argument চুপচাপ উপেক্ষিত হয়। addVat(500, 0.15, "হ্যালো") ঠিকঠাক ৫৭৫ দেয়।

এই নরম আচরণ সুবিধাও, ফাঁদও। সুবিধা: ঐচ্ছিক (optional) parameter রাখা সহজ। ফাঁদ: ভুল করে একটি argument বাদ পড়লে প্রোগ্রাম ভাঙে না, বরং অনেক দূরে গিয়ে একটি অদ্ভুত NaN দেখায় — তখন উৎস খুঁজে পাওয়া কঠিন। এই ফাঁদ থেকে বাঁচার প্রথম অস্ত্র default parameter (অংশ ৪), আর বড় অস্ত্র TypeScript (পর্ব ৪৭)।

return না থাকলে — undefined

প্রতিটি JavaScript function কিছু না কিছু ফেরত দেয়। return না লিখলে, বা শুধু return; লিখলে, ফেরত আসে undefined. তাই const x = console.log("hi"); লিখলে x হয় undefined — console.log ছাপে, কিন্তু কিছু ফেরত দেয় না।

return-এর পরে লাইন ভাঙবেন না। নিচের function কী ফেরত দেয়?

function getUser() {
  return
    { name: "Rafi" };
}

উত্তর undefined! পর্ব ০৪-এর ASI নিয়ম অনুযায়ী engine return-এর পরে লাইন ভাঙা দেখে সেখানেই সেমিকোলন বসিয়ে দেয়: return;. নিচের object আর কখনো পৌঁছায় না। সমাধান: খোলা বন্ধনী { বা ( return-এর একই লাইনে রাখুন। Prettier (পর্ব ৪৬) এটা নিজে থেকেই ঠিক রাখে।

৩ · তিন রকম লেখা — declaration, expression আর arrow

JavaScript-এ একই কাজের function তিনভাবে লেখা যায়। তিনটিই কাজ করে, কিন্তু আচরণে কিছু গুরুত্বপূর্ণ পার্থক্য আছে।

// ১. Function declaration — "function" শব্দ দিয়ে statement শুরু
function square(x) {
  return x * x;
}

// ২. Function expression — function একটি মান, সেটি variable-এ রাখা
const square2 = function (x) {
  return x * x;
};

// ৩. Arrow function (ES2015) — ছোট রূপ
const square3 = (x) => x * x;

তিনটিই square(4)-এর মতো ডাকলে ১৬ দেয়। Function declaration একটি statement — লাইনটি function শব্দ দিয়ে শুরু। Function expression-এ function একটি expression, অর্থাৎ একটি মান তৈরি করে, আর সেই মান আমরা const variable-এ রাখি। Arrow function হলো expression-এরই ছোট রূপ — function শব্দ নেই, তার বদলে => চিহ্ন।

Arrow function-এর লেখার নিয়ম

রূপউদাহরণমন্তব্য
parameter নেই() => "হ্যালো"খালি বন্ধনী লাগবেই
একটি parameterx => x * 2বন্ধনী ঐচ্ছিক; Prettier ডিফল্টে বন্ধনী দেয়: (x) =>
একাধিক parameter(a, b) => a + bবন্ধনী লাগবেই
এক expression — implicit return(a, b) => a + b{ } নেই, তাই return না লিখলেও ফল ফেরত আসে
একাধিক লাইন — block body(a, b) => { const s = a + b; return s; }{ } দিলে return নিজে লিখতে হবে
object ফেরত() => ({ taka: 100 })object-কে গোল বন্ধনীতে মুড়তে হবে — নিচে কারণ

Object ফেরতের ফাঁদ: const f = () => { taka: 100 }; লিখলে f() দেয় undefined, object নয়। কারণ =>-এর পরে { দেখলে engine ধরে নেয় এটি block body-র শুরু। তখন ভেতরের taka: হয়ে যায় একটি “label” (লুপ থেকে বেরোনোর জন্য পুরোনো একটি ব্যাকরণ), আর 100 একটি অর্থহীন expression. Block-এ কোনো return নেই — তাই undefined. সমাধান: () => ({ taka: 100 }). গোল বন্ধনী engine-কে বলে, “এটি একটি expression, block নয়।” নিচের প্রথম runnable কোডে নিজেই দেখবেন।

Hoisting-এর পার্থক্য — একটুখানি recap

পর্ব ০৭-এ hoisting বিস্তারিত দেখেছেন। Function-এর ক্ষেত্রে এর ফল সরাসরি চোখে পড়ে:

লেখাঘোষণার আগের লাইনে ডাকলে
function f() {}কাজ করে — পুরো function আগে থেকেই তৈরি থাকে
const f = function () {} বা const f = () => {}ReferenceError: Cannot access 'f' before initialization (TDZ)
var f = function () {}TypeError: f is not a function — f আছে, কিন্তু মান তখনো undefined

এই বার্তাগুলো V8-এর (Chrome, Edge, Node.js). Declaration-এর এই সুবিধার জন্য অনেকে ফাইলের ওপরে মূল কাজ আর নিচে helper function লেখেন — পড়তে সুবিধা হয়।

তাহলে কোনটি কখন?

Declaration

ফাইলের মূল, নাম দেওয়া function — formatTaka, validatePhone. Stack trace-এ নাম স্পষ্ট দেখা যায়, hoisting-এর সুবিধা আছে।

Arrow

ছোট callback — prices.map((p) => p * 2), setTimeout(() => …). বাইরের this ধরে রাখতে চাইলেও (অংশ ৭)।

Expression

শর্তসাপেক্ষে function বানাতে, বা const দিয়ে নিশ্চিত হতে যে নামটি পরে বদলাবে না। আজকাল বেশিরভাগ জায়গায় arrow এর জায়গা নিয়েছে।

Object-এর method

Arrow নয় — method shorthand { total() { … } }, যাতে this ঠিক থাকে (অংশ ৭ আর পর্ব ১৪)।

C/C++/Python/Java জানলে

C-তে function শুধু কোডের নাম, পাঠাতে হলে function pointer লাগে (C-এর function); JavaScript-এ function সরাসরি মান। Python-এর মতোই এখানে default parameter আছে, তবে বড় পার্থক্য: Python default মান একবারই তৈরি করে, JavaScript প্রতিটি কলে নতুন করে হিসাব করে — তাই items = [] default নিরাপদ (Python-এর function). Java-তে যেমন overloading আছে, JavaScript-এ তেমন নেই — একই নামে দ্বিতীয় function লিখলে প্রথমটি মুছে যায়; Java-র varargs int... xs-এর জায়গায় এখানে rest ...xs (Java-র method). আর argument সংখ্যা কেউ যাচাই করে না।

৪ · Default parameter — কখন ডিফল্ট মান খাটে

অংশ ২-এ দেখেছেন, argument বাদ পড়লে parameter undefined হয়। ES2015 (ES6) থেকে আপনি parameter-এর পাশে একটি default parameter মান লিখে দিতে পারেন: function addVat(amount, rate = 0.15). Argument না এলে rate হবে ০.১৫.

কিন্তু “না এলে” মানে ঠিক কী? এখানেই সূক্ষ্ম নিয়মটি: default খাটে শুধু তখন, যখন argument-এর মান undefined. বাদ দিলে undefined, নিজে undefined পাঠালেও undefined — দুই ক্ষেত্রেই default. কিন্তু null, 0, "" বা false পাঠালে default খাটে না; পাঠানো মানটিই থাকে। চার রকম কল ধাপে ধাপে দেখুন:

1function addVat(amount, rate = 0.15) {2  return amount + amount * rate;3}4addVat(1000);5addVat(1000, undefined);6addVat(1000, null);7addVat(1000, 0);
rate কোথা থেকে মান পেল
পাঠানো argument
default চলল?
rate
হিসাব
  1. নিয়মটি। Function ডাকার সময় engine প্রতিটি parameter দেখে: “এর argument কি undefined?” হ্যাঁ হলে =-এর ডান পাশের expression তখনই হিসাব করে বসায়। না হলে পাঠানো মানই থাকে।
  2. addVat(1000) — argument বাদ। দ্বিতীয় argument নেই, তাই rate প্রথমে undefined → default চলল → 0.15. ফল 1150.
  3. addVat(1000, undefined) — নিজে undefined পাঠানো। Engine-এর চোখে “বাদ দেওয়া” আর “undefined পাঠানো” একই। আবার default → 1150. মাঝের কোনো parameter-কে default-এ রেখে পরেরটিতে মান দিতে চাইলে এই কৌশল কাজে লাগে।
  4. addVat(1000, null) — ফাঁদ! null মানে “ইচ্ছা করে খালি” — এটি undefined নয়, তাই default চলে না। rate থাকে null. গুণের সময় পর্ব ০৬-এর coercion-এ null হয় ০, তাই ফল 1000 — VAT উধাও, কোনো error নেই। API বা ডেটাবেস থেকে আসা ডেটায় null খুব সাধারণ, তাই এই বাগ বাস্তবে প্রায়ই ঘটে।
  5. addVat(1000, 0) — ০ একটি বৈধ মান। VAT-মুক্ত পণ্যের জন্য হার ০ পাঠানো হলো। Default চলে না (০ undefined নয়), ফল 1000 — এবার এটাই ঠিক। পুরোনো কৌশল rate = rate || 0.15 এখানে ভুল করত: ০ falsy, তাই হার হয়ে যেত ০.১৫. Default parameter আর ?? (পর্ব ০৮) — দুটোই এই ভুল এড়ায়।
মূল ধারণা

Default parameter = “argument undefined হলে এই মান নাও।” null-কেও ডিফল্টে পাঠাতে চাইলে body-তে লিখুন rate = rate ?? 0.15; — ?? null আর undefined দুটোই ধরে, কিন্তু ০-কে ছোঁয় না।

Default নিয়ে আরও তিনটি কাজের কথা:

  • প্রতিটি কলে নতুন করে হিসাব হয়। function addItem(item, list = []) — প্রতিবার নতুন খালি array. (Python-এ এটি একটি বিখ্যাত বাগ, JavaScript-এ নয়।)
  • আগের parameter ব্যবহার করা যায়। function box(width, height = width) — উচ্চতা না দিলে বর্গ।
  • Default আছে এমন parameter শেষে রাখুন। function f(a = 1, b) লেখা বৈধ, কিন্তু b দিতে গেলে f(undefined, 5) লিখতে হয় — বিরক্তিকর। অনেক ঐচ্ছিক option থাকলে একটি object নিন: createUser({ name, role = "user" }) — এই destructuring পর্ব ১৭-এ।

৫ · Rest parameter, পুরোনো arguments আর spread

একটি কার্টে কতগুলো পণ্য থাকবে, আগে থেকে জানা নেই। sum(120, 80), sum(120, 80, 250, 40) — যত খুশি দাম নিতে পারে এমন function কীভাবে লিখবেন? উত্তর rest parameter: শেষ parameter-এর আগে তিনটি ডট।

function sum(...prices) {           // prices একটি আসল array
  let total = 0;
  for (const p of prices) total += p;
  return total;
}
sum(120, 80, 250);   // prices = [120, 80, 250] → 450
sum();               // prices = [] → 0

function bill(customer, ...items) { }  // প্রথমটি আলাদা, বাকি সব items-এ

...prices বলে: “যত argument আসবে, সব একটি array-তে ভরে আমাকে দাও।” কোনো argument না এলে খালি array [] — কখনো undefined নয়, তাই লুপ নিরাপদে চলে। Rest parameter শুধু একটি থাকতে পারে আর সেটি সবশেষে — function f(...a, b) লিখলে SyntaxError.

পুরোনো উপায় — arguments object

ES2015-এর আগে rest ছিল না। তখন প্রতিটি সাধারণ function-এর ভেতরে স্বয়ংক্রিয়ভাবে একটি arguments নামের object থাকত (এখনো থাকে), যাতে সব argument জমা থাকে। পুরোনো কোড আর লাইব্রেরিতে এটি প্রচুর দেখবেন। কিন্তু এর তিনটি বড় সমস্যা:

  1. এটি array নয়, “array-এর মতো”। arguments.length আর arguments[0] আছে, কিন্তু map, filter-এর মতো array method নেই। আগে Array.from(arguments) দিয়ে বদলাতে হতো।
  2. Arrow function-এ নিজস্ব arguments নেই। Arrow-এর ভেতরে arguments লিখলে সেটি বাইরের সাধারণ function-এর arguments ধরে; বাইরে কোনো function না থাকলে ব্রাউজারের সাধারণ script-এ ReferenceError: arguments is not defined.
  3. নাম দেখে বোঝা যায় না। function sum() দেখে কেউ বুঝবে না যে এটি argument নেয়; function sum(...prices) নিজেই বলে দেয়।

তাই নতুন কোডে সবসময় rest parameter. এর পেছনের ইতিহাস ভাবনার প্রশ্ন ৪-এ।

উল্টো দিক — spread দিয়ে ডাকা

Rest অনেক argument-কে একটি array-তে জড়ো করে। ঠিক উল্টো কাজ করে spread — ডাকার সময় একটি array-কে আলাদা আলাদা argument-এ ছড়িয়ে দেয়:

const prices = [120, 80, 250];
Math.max(...prices);     // Math.max(120, 80, 250) → 250
sum(...prices, 40);      // sum(120, 80, 250, 40) → 490

চিহ্ন একই (...), কিন্তু অবস্থান দেখে কাজ বোঝা যায়: parameter-এর তালিকায় থাকলে rest (জড়ো), কলের ভেতরে বা array/object-এর ভেতরে থাকলে spread (ছড়ানো)। Spread-এর পুরো ব্যবহার পর্ব ১৭-এ।

এবার default, rest আর arrow-এর object ফাঁদ — তিনটিই নিজে চালিয়ে দেখুন:

// উদাহরণ: ধরে নেওয়া VAT হার 15%
function addVat(amount, rate = 0.15) {
  return amount + amount * rate;
}
console.log(addVat(1000), addVat(1000, undefined));
console.log(addVat(1000, null), addVat(1000, 0.05));

function sum(...prices) {
  let total = 0;
  for (const p of prices) total += p;
  return total;
}
const cart = [120, 80, 250];
console.log(sum(...cart), sum(), Array.isArray([...cart]));

const bad = () => { taka: 100 };
const good = () => ({ taka: 100 });
console.log(bad(), good());

function old() { return Array.isArray(arguments) + " " + arguments.length; }
console.log(old("a", "b"), addVat.length, sum.length);

প্রথম লাইনে 1150 1150 — বাদ দেওয়া আর undefined পাঠানো একই। দ্বিতীয় লাইনে 1000 1050 — null default চালায়নি, VAT হারিয়ে গেছে। তৃতীয় লাইনে 450 0 true: spread দিয়ে array ছড়িয়ে পাঠানো, খালি কলে rest পেল []. চতুর্থ লাইনে undefined { taka: 100 } — বন্ধনী ছাড়া object হারিয়ে যায়। শেষ লাইনে false 2 1 0: arguments array নয়; আর function-এর length গোনে শুধু প্রথম default বা rest parameter-এর আগের parameter — তাই addVat.length ১, sum.length ০.

৬ · Function-ও একটি মান — callback, higher-order function আর function ফেরত দেওয়া

এবার JavaScript-এর সবচেয়ে শক্তিশালী ধারণা। একটি চেকআউট function ভাবুন। দাম যোগ করার কাজ সব সময় একই, কিন্তু ছাড় (discount) বদলায় — ঈদের অফারে ১০%, নতুন গ্রাহকের জন্য ১০০ টাকা কম, bKash দিয়ে দিলে আলাদা ক্যাশব্যাক। প্রতিটি অফারের জন্য আলাদা চেকআউট function লিখবেন? না। বরং ছাড়ের নিয়মটি একটি function হিসেবে চেকআউটের হাতে দিন, আর চেকআউট মোট দাম বের করে সেই function-কে ডাকুক।

যে function আপনি অন্য function-এ পাঠান, যাতে সে পরে আপনার function-কে “ফিরে ডাকে”, সেটি callback. আর যে function অন্য function নেয় বা ফেরত দেয়, সেটি higher-order function. ধাপে ধাপে দেখুন:

1const tenOff = (t) => t * 0.9;2function checkout(cart, discountFn) {3  let total = 0;4  for (const item of cart) total += item.price;5  return discountFn(total);6}7console.log(checkout([{price: 700}, {price: 500}], tenOff));
Call stack আর মান
stack
tenOff
discountFn
total
t
  1. Function তৈরি, ডাকা নয়। লাইন ১-এ একটি arrow function তৈরি হয়ে tenOff variable-এ বসল — ঠিক যেমন একটি number বসে। কোনো হিসাব হয়নি; tenOff শুধু “কীভাবে ছাড় দিতে হয়” তার রেসিপি ধরে রেখেছে।
  2. Function-কে argument হিসেবে পাঠানো। লাইন ৭-এ checkout ডাকা হলো। খেয়াল করুন, পাঠানো হয়েছে tenOff — বন্ধনী ছাড়া। tenOff() লিখলে আগে ডাকা হতো আর পাঠানো হতো তার ফল। এখন discountFn আর tenOff heap-এ একই function দেখায়।
  3. Checkout নিজের কাজ করছে। লুপে 700 + 500 = 1200. চেকআউট জানে না ছাড় কীভাবে দেওয়া হবে — জানার দরকারও নেই।
  4. Call back — ফিরে ডাকা। লাইন ৫-এ discountFn(total). এবার বন্ধনী আছে, তাই আসলেই ডাকা হলো। tenOff-এর frame বসল checkout-এর ওপরে, t = 1200. আপনার দেওয়া function আপনার কোডে নয়, চেকআউটের ভেতর থেকে চলল — এজন্যই নাম “callback”.
  5. ফল ফিরে আসা। tenOff ফেরত দিল 1080, তার frame pop. checkout সেই মানই return করল, তার frame-ও pop.
  6. ছাপা। আউটপুটে 1080. অন্য অফারের জন্য শুধু আরেকটি ছোট function লিখে পাঠালেই চলবে — চেকআউটে হাত দিতে হবে না।
tenOff = function-টি নিজে (মান)
tenOff() = function ডেকে পাওয়া ফল

যেসব জায়গায় আপনি আগেই callback দেখছেন

  • সাজানোর নিয়ম: [10, 9, 100].sort() দেয় [10, 100, 9] — কারণ ডিফল্টে sort সংখ্যাকে string বানিয়ে অভিধানের ক্রমে সাজায়। সংখ্যা হিসেবে সাজাতে একটি তুলনার function (comparator) পাঠান: .sort((a, b) => a - b). ফল ঋণাত্মক হলে a আগে, ধনাত্মক হলে b আগে।
  • সময় পরে চালানো: setTimeout(() => console.log("চা তৈরি"), 2000) — দুই সেকেন্ড পরে ব্রাউজার আপনার function ডাকবে (পর্ব ৩২)।
  • ক্লিক: button.addEventListener("click", handleClick) — ক্লিক হলে handleClick চলবে (পর্ব ৩৮)। এখানেও বন্ধনী নেই!
  • Array method: prices.map((p) => p * 2), filter, reduce — পর্ব ১৬-এ পুরোটা।

Function ফেরত দেওয়া — function বানানোর কারখানা

Higher-order function-এর অন্য দিক: একটি function নতুন function তৈরি করে ফেরত দিতে পারে।

function checkout(cart, discountFn = (t) => t) {
  let total = 0;
  for (const item of cart) total += item.price * item.qty;
  return discountFn(total);
}
const tenOff = (t) => t * 0.9;
const flat100 = (t) => Math.max(0, t - 100);
const cart = [{ price: 450, qty: 2 }, { price: 300, qty: 1 }];
console.log(checkout(cart), checkout(cart, tenOff), checkout(cart, flat100));

function makeMultiplier(k) {
  return (x) => x * k;          // নতুন function ফেরত
}
const double = makeMultiplier(2);
const triple = makeMultiplier(3);
console.log(double(21), triple(5), typeof double);

console.log([10, 9, 100].sort(), [10, 9, 100].sort((a, b) => a - b));

প্রথম লাইনে 1200 1080 1100: একই চেকআউট, তিন রকম ছাড়। Callback না দিলে default (t) => t — দাম যেমন ছিল তেমন। দ্বিতীয় লাইনে 42 15 function: makeMultiplier(2) একটি নতুন function বানিয়ে দিল যেটি যেকোনো সংখ্যাকে ২ দিয়ে গুণ করে। আশ্চর্যের বিষয়, makeMultiplier শেষ হয়ে যাওয়ার পরেও double মনে রেখেছে k = 2 — এটাই closure, যার পুরো গল্প পর্ব ২২-এ। শেষ লাইনে [ 10, 100, 9 ] [ 9, 10, 100 ] — ডিফল্ট sort string হিসেবে সাজায়, comparator দিলে সংখ্যা হিসেবে।

IIFE — একটি পুরোনো কৌশল

পুরোনো কোডে প্রায়ই দেখবেন এমন অদ্ভুত লেখা: (function () { … })();. এটি IIFE — Immediately Invoked Function Expression, অর্থাৎ তৈরি হয়েই সাথে সাথে ডাকা function. কেন? ES2015-এর আগে let, const বা module ছিল না; var-এর scope শুধু function পর্যন্ত (পর্ব ০৭)। তাই একটি ফাইলের variable যাতে পুরো পাতার global-এ ছড়িয়ে না পড়ে, পুরো কোড একটি function-এ মুড়ে সাথে সাথে চালানো হতো। jQuery-র যুগের অনেক plugin এভাবে লেখা। আজ এই কাজ করে block scope আর ES module (পর্ব ৪৫), তাই নতুন কোডে IIFE খুব কম দরকার — কিন্তু পুরোনো কোড পড়তে চিনে রাখুন।

৭ · Arrow function আর this — কোথায় arrow নয়

Arrow function শুধু ছোট লেখা নয়; আচরণেও আলাদা। সবচেয়ে বড় পার্থক্য: arrow function-এর নিজের this নেই। এর ভেতরে this লিখলে সেটি বাইরের scope-এর this নেয় — যেখানে arrow-টি লেখা হয়েছে, সেখানকার। একে বলে lexical this. this কী জিনিস, কীভাবে ঠিক হয় — পুরো বিষয়টি পর্ব ২৪-এ। এখানে শুধু একটি নিয়ম মনে রাখুন, কারণ AI আর নতুনরা এই ভুল সবচেয়ে বেশি করে:

const shop = {
  name: "রহিম স্টোর",
  greetArrow: () => "স্বাগতম " + this.name,      // ✘ বাইরের this
  greet() { return "স্বাগতম " + this.name; },     // ✔ shop
  later() {
    setTimeout(() => console.log(this.name), 100); // ✔ arrow এখানে ঠিক
  },
};
shop.greet();       // "স্বাগতম রহিম স্টোর"
shop.greetArrow();  // "স্বাগতম undefined" বা "স্বাগতম " — shop নয়!

প্রত্যাশিত আউটপুট মন্তব্যে লেখা। greetArrow object-এর ভেতরে লেখা হলেও object literal নিজে কোনো scope তৈরি করে না — তাই এর this হলো ফাইলের বাইরের this (module-এ undefined, যেখানে this.name পড়তে গেলে TypeError আসে; সাধারণ ব্রাউজার script-এ window, যার name সাধারণত খালি string; Node.js-এর CommonJS ফাইলে খালি object {}, তাই undefined). কোনোটিই shop নয়। অন্যদিকে later-এর ভেতরের arrow ঠিক এই কারণেই সুবিধাজনক: সে later-এর this, অর্থাৎ shop, ধরে রাখে। Callback-এ arrow ভালো, method-এ খারাপ।

বৈশিষ্ট্যসাধারণ functionArrow function
নিজের thisআছে — কীভাবে ডাকা হলো তার ওপর নির্ভর করেনেই — বাইরের scope-এর this
নিজের argumentsআছেনেই — rest ব্যবহার করুন
new দিয়ে ডাকাযায় (constructor)যায় না — TypeError
Hoistingdeclaration হলে পুরোটাvariable-এর নিয়ম (const হলে TDZ)
সেরা ব্যবহারনাম দেওয়া মূল function, object methodছোট callback, বাইরের this দরকার এমন জায়গা

৮ · Pure function, side effect আর ভালো function-এর অভ্যাস

একটি pure function-এর দুটি গুণ: (১) একই input দিলে সবসময় একই output দেয়, আর (২) বাইরের কিছু বদলায় না। addVat pure — ১০০০ দিলে সবসময় ১১৫০, আর কিছু ছোঁয় না। বাইরের কিছু বদলানো — ফাইলে লেখা, পাতা বদলানো, global variable বাড়ানো, এমনকি console.log — এসবকে বলে side effect.

Side effect খারাপ নয়; এগুলো ছাড়া প্রোগ্রাম কিছুই দেখাতে পারত না। কিন্তু বিপজ্জনক ধরনটি হলো পাওয়া input গোপনে বদলে দেওয়া:

// ✘ input বদলে দেয় — যে cart পাঠিয়েছে, তার cart-ও বদলে যায়
function addItemBad(cart, item) {
  cart.push(item);
  return cart;
}

// ✔ নতুন array ফেরত দেয় — মূল cart অক্ষত
function addItem(cart, item) {
  return [...cart, item];
}

Array আর object function-এ যায় reference হিসেবে — function মূল জিনিসটিরই ঠিকানা পায়, কপি নয় (পর্ব ১৪-এ বিস্তারিত)। তাই addItemBad ডাকলে কলকারীর নিজের cart-ও বদলে যায়। দশ জায়গা থেকে একই cart ব্যবহার হলে কে কখন বদলাল, খুঁজে পাওয়া কঠিন। addItem spread দিয়ে একটি নতুন array বানায় — মূলটি অক্ষত থাকে। React-এর মতো framework ঠিক এই নিয়মের ওপর দাঁড়িয়ে (পর্ব ৫৪)। Functional programming-এর বিস্তারিত পর্ব ২৩-এ।

ছোট, এক-কাজের, নাম দেখেই বোঝা যায়

বাস্তব প্রজেক্টে ভালো function দেখতে কেমন? তিনটি ছোট helper দেখুন — একটি টাকা দেখানোর, একটি বাংলাদেশি মোবাইল নম্বর যাচাইয়ের:

/**
 * টাকার অঙ্ক বাংলাদেশি ধাঁচে কমা দিয়ে দেখায়।
 * @param {number} amount - টাকার পরিমাণ
 * @returns {string} যেমন "৳12,50,000"
 */
function formatTaka(amount) {
  return "৳" + amount.toLocaleString("en-IN");
}

/**
 * বাংলাদেশি মোবাইল নম্বর (01 দিয়ে শুরু, মোট 11 অঙ্ক) কি না।
 * @param {string} phone
 * @returns {boolean}
 */
function isValidBdPhone(phone) {
  return /^01[3-9]\d{8}$/.test(phone);
}

formatTaka(1250000) দেয় "৳12,50,000" — "en-IN" লাখ-কোটির ধাঁচে কমা দেয়, যা বাংলাদেশেও প্রচলিত (বাংলা অঙ্কে দেখানো পর্ব ২০-এ)। isValidBdPhone("01712345678") দেয় true, ১০ অঙ্কের নম্বরে false. Regular expression-টি এখন না বুঝলেও চলবে (পর্ব ৩০)। লক্ষ করুন: দুটিই pure, প্রতিটির একটিমাত্র কাজ, নাম ক্রিয়াপদ দিয়ে শুরু (format…, is…), আর ওপরে JSDoc comment.

JSDoc-এর @param আর @returns শুধু মানুষের জন্য নয়। VS Code এগুলো পড়ে function ডাকার সময় parameter-এর নাম আর type দেখায়, আর ফাইলের ওপরে // @ts-check লিখলে type-এর ভুলও ধরে। এটা TypeScript-এর প্রথম ধাপ — পর্ব ৪৭-এ আরও এগোবেন।

ভালো function-এর পাঁচ অভ্যাস
  • একটি কাজ: নাম দিতে গিয়ে “এবং” লাগলে (validateAndSaveAndEmail) ভেঙে ফেলুন।
  • নাম ক্রিয়া দিয়ে: calculateTotal, formatDate; boolean ফেরত দিলে is/has দিয়ে।
  • ছোট: এক স্ক্রিনে না ধরলে সম্ভবত বেশি বড়।
  • কম parameter: তিনটির বেশি হলে একটি option object নিন।
  • Input বদলাবেন না; নতুন মান ফেরত দিন।

৯ · Recursion — নিজেকে ডাকা function

একটি function নিজেকেও ডাকতে পারে — একে বলে recursion. ক্লাসিক উদাহরণ factorial: ৩! = ৩ × ২ × ১. লক্ষ করুন, ৩! = ৩ × ২!, আর ২! = ২ × ১!. অর্থাৎ বড় সমস্যাটি একই রকম ছোট সমস্যার ওপর দাঁড়িয়ে। শুধু একটি জায়গায় থামতে হবে — ১! = ১. এই থামার জায়গাটি হলো base case.

1function factorial(n) {2  if (n <= 1) return 1;          // base case3  return n * factorial(n - 1);4}5console.log(factorial(3));
Call stack (ওপরেরটি এখন চলছে)
stack
অপেক্ষমাণ হিসাব
  1. factorial(3) শুরু। n = 3; base case নয়। লাইন ৩-এ 3 * factorial(2) — গুণ করার আগে factorial(2)-এর ফল লাগবে, তাই factorial(3) অর্ধেক কাজ রেখে অপেক্ষা করছে।
  2. factorial(2) — নতুন frame. একই function, কিন্তু নতুন frame আর নিজস্ব n = 2. আগের frame-এর n = 3 নিচে অক্ষত। এবার অপেক্ষা factorial(1)-এর জন্য।
  3. factorial(1) — base case. n <= 1 সত্য, তাই আর নিজেকে ডাকে না; সরাসরি 1 ফেরত দেয়। Base case না থাকলে frame জমতেই থাকত, শেষে পর্ব ০৫-এর RangeError: Maximum call stack size exceeded.
  4. উল্টো দিকে খোলা শুরু। factorial(1)-এর frame pop. factorial(2) এতক্ষণ যে গুণের অপেক্ষায় ছিল, সেটি এখন শেষ করতে পারে: 2 * 1 = 2, ফেরত দেয় ২।
  5. factorial(3) শেষ করে। 3 * 2 = 6. প্রথমে শুরু হওয়া কলটি সবার শেষে শেষ হলো — stack-এর LIFO নিয়ম।
  6. ফল ছাপা। আউটপুটে 6. মোট ৩টি frame একসাথে stack-এ ছিল; factorial(10000)-এ থাকত ১০ হাজারটি।

Recursion-এর দুটি নিয়ম: (১) একটি base case থাকতে হবে, (২) প্রতিটি কলে সমস্যা base case-এর দিকে ছোট হতে হবে। Factorial-এর মতো সরল কাজ একটি লুপেও করা যায়, আর লুপ stack ভরায় না। Recursion সত্যিই উজ্জ্বল হয় গাছের মতো গঠনে — ফোল্ডারের ভেতরে ফোল্ডার, মন্তব্যের উত্তরে উত্তর, পাতার DOM (পর্ব ৩৭), নেস্টেড JSON (পর্ব ১৯)।

খুব গভীর recursion: JavaScript-এ call stack-এর সীমা সাধারণত কয়েক হাজার থেকে কয়েক দশ হাজার frame (ব্রাউজার আর frame-এর আকারভেদে)। ES2015-এর মানদণ্ডে “proper tail call” নামে একটি নিয়ম আছে, যাতে শেষ কাজ হিসেবে করা recursion নতুন frame না বাড়ায়। কিন্তু প্রধান engine-গুলোর মধ্যে শুধু Safari-র JavaScriptCore এটি চালু রেখেছে; Chrome, Edge, Node.js আর Firefox-এ নেই। তাই খুব গভীর ডেটায় লুপ ব্যবহার করুন — বিস্তারিত ভাবনার প্রশ্ন ৫-এ।

পরীক্ষা ও ইন্টারভিউতে
  • Function declaration আর expression-এর পার্থক্য কী? — Declaration পুরোটা hoist হয়, তাই ঘোষণার আগেও ডাকা যায়; expression একটি মান, variable-এর নিয়ম মানে (const হলে TDZ-এ ReferenceError).
  • Arrow function কোথায় সাধারণ function থেকে আলাদা? — নিজের this ও arguments নেই, new দিয়ে ডাকা যায় না, এক expression হলে implicit return.
  • f(null) দিলে default parameter খাটে কি? — না; default খাটে শুধু undefined-এ। null-কেও ধরতে ?? ব্যবহার করুন।
  • Rest parameter আর arguments-এর পার্থক্য? — Rest আসল array, নাম দেওয়া, arrow-এও কাজ করে; arguments array-এর মতো object, arrow-এ নেই।
  • Higher-order function কী? — যে function অন্য function argument হিসেবে নেয় বা ফেরত দেয়, যেমন map, setTimeout, makeMultiplier.

১০ · AI-এর যুগে — AI-এর লেখা function যাচাই করা

AI-কে “একটি checkout feature লিখে দাও” বললে প্রায়ই পাবেন একটিমাত্র ১৫০ লাইনের function — দাম যোগ, ছাড়, VAT, ডেটাবেসে লেখা, ইমেল পাঠানো সব একসাথে। কাজ হয়তো করে, কিন্তু পরীক্ষা করা, বদলানো আর পুনরায় ব্যবহার করা প্রায় অসম্ভব। Function-এর বিষয়ে AI-এর সবচেয়ে সাধারণ ভুলগুলো:

AI-এর ভুলকেন সমস্যাকী চাইবেন
সব কাজ এক বিশাল function-এএকটি অংশ বদলাতে পুরোটা বুঝতে হয়; টেস্ট করা কঠিনপ্রতিটি কাজের জন্য আলাদা ছোট function
Object method-এ arrow functionthis object-কে না দেখিয়ে বাইরের this দেখায়method shorthand total() { }
পাওয়া array/object বদলে দেওয়া (push, sort, property বসানো)কলকারীর ডেটা গোপনে বদলায়নতুন মান ফেরত — spread, toSorted
rate = 0.15 ধরে নেওয়া যে null-এও খাটবেAPI থেকে null এলে চুপচাপ ভুল হিসাব?? বা স্পষ্ট যাচাই
পুরোনো arguments বা rate || 0.15০ বৈধ মান হলেও default বসে যায়rest parameter, default parameter
JSDoc-এ এক কথা, কোডে আরেকনথি মিথ্যা বললে পাঠক ভুল পথে যায়নথি আর কোড মিলিয়ে দেখা

AI-কে function লেখাতে চাইলে চুক্তিটা আগে থেকে বলে দিন — কী ঢুকবে, কী বেরোবে, কী বদলানো চলবে না:

একটি JavaScript function লিখে দাও: একটি অনলাইন দোকানের চেকআউটের মোট দাম বের করবে। নিয়ম: ১. প্রতিটি function একটি কাজ করবে — মোট যোগ, ছাড়, VAT আলাদা function-এ। কোনো function ২০ লাইনের বেশি নয়। ২. কোনো function পাওয়া array বা object বদলাবে না (push, sort, property বসানো নিষেধ)। ৩. ছাড়ের নিয়ম callback হিসেবে নেবে। ৪. Default parameter দিলে বলে দাও null এলে কী হবে, আর null-কে কীভাবে সামলাবে। ৫. Object-এর method-এ arrow function দেবে না। ৬. প্রতিটি function-এর ওপরে JSDoc (@param, @returns) দাও। ৭. শেষে ৫টি console.log টেস্ট দাও — খালি কার্ট, null হার, ০ হার, ছাড় না দেওয়া, আর একটি সাধারণ কেস — প্রত্যেকটির প্রত্যাশিত ফলসহ।

AI-এর লেখা function হাতে পেলে — যাচাইয়ের চেকলিস্ট

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন।

প্র ০১দেখতে Java-র মতো হলেও JavaScript-এর function-এর চিন্তা এসেছে Scheme নামের একটি ভাষা থেকে — এর মানে কী, আর এর প্রভাব আজও কোথায়?

১৯৯৫ সালে Netscape যখন Brendan Eich-কে নিয়োগ দেয়, তাঁকে প্রথমে বলা হয়েছিল ব্রাউজারে “Scheme বসানোর” কথা। Scheme হলো Lisp পরিবারের একটি ছোট, সুন্দর ভাষা, যা ১৯৭০-এর দশকে MIT-তে Guy Steele আর Gerald Sussman তৈরি করেন। Scheme-এর কেন্দ্রে আছে function: function একটি মান, তাকে পাঠানো যায়, ফেরত দেওয়া যায়, আর function তার জন্মস্থানের variable মনে রাখে (lexical scope ও closure).

কিন্তু Netscape-এর ব্যবস্থাপনা তখন Sun Microsystems-এর সাথে Java নিয়ে চুক্তি করছিল, আর তারা চাইল নতুন ভাষাটি “দেখতে Java-র মতো” হোক — বাঁকা বন্ধনী, if/for, C-ধাঁচের ব্যাকরণ। Eich তাই একটি আপস করেন: বাইরে Java-র পোশাক, ভেতরে Scheme-এর function আর Self নামের আরেকটি ভাষার prototype. ১৯৯৫ সালের মে মাসে মাত্র প্রায় দশ দিনে প্রথম রূপটি তৈরি হয়।

এই আপসের ফল আজও চোখের সামনে। শুরুর Java-তে method-কে মান হিসেবে পাঠানোর সহজ উপায় ছিল না; Java-তে lambda এসেছে ২০১৪ সালে, Java 8-এ। অথচ JavaScript-এ প্রথম দিন থেকেই button.onclick = function () { … } লেখা যেত। এই কারণেই ব্রাউজারের event-ভিত্তিক মডেল JavaScript-এ এত স্বাভাবিক লাগে, আর এই কারণেই Node.js-এর callback-নির্ভর নকশা সম্ভব হয়েছিল।

অনেক বছর ধরে মানুষ JavaScript-কে “খেলনা Java” ভেবে ভুলভাবে লিখত — class-এর মতো করে সাজানোর চেষ্টা করত। ২০০৮ সালে Douglas Crockford-এর বই “JavaScript: The Good Parts” জনপ্রিয় করে এই কথা যে ভাষাটির আসল শক্তি তার function-এ, আর অনেকে JavaScript-কে “C-এর পোশাকে Lisp” বলে বর্ণনা করতে শুরু করেন। তারপর এল jQuery, Underscore, Lodash, আর শেষে ES5-এর map/filter/reduce — সবই function পাঠানোর ওপর দাঁড়ানো। আজ React-এর component আর hook-ও মূলত function.

মূল উপলব্ধি: JavaScript-কে ভালো বুঝতে হলে ব্যাকরণ দেখে নয়, function দেখে বুঝুন। বাইরের চেহারা Java-র, কিন্তু চিন্তার পদ্ধতি Scheme-এর — function একটি মান, আর সেটিই ভাষার কেন্দ্র।

প্র ০২“First-class function” থাকাটা কেন এত বড় ব্যাপার? একটি ভাষায় function মান না হলে কী কী অসুবিধা হয়?

একটি ভাষায় কোনো জিনিসকে “first-class” বলা হয় যখন তার সাথে অন্য যেকোনো মানের মতো আচরণ করা যায়: variable-এ রাখা, argument হিসেবে পাঠানো, ফেরত দেওয়া, data structure-এ রাখা, চলার সময় নতুন তৈরি করা। ১৯৬০-এর দশকে ব্রিটিশ কম্পিউটার বিজ্ঞানী Christopher Strachey এই ধারণাটির নাম দেন।

এখন ভাবুন একটি ভাষায় function মান নয়। আপনাকে একটি “সাজানো” function লিখতে হবে যা দাম অনুযায়ী, নাম অনুযায়ী, তারিখ অনুযায়ী — যেকোনো নিয়মে সাজাতে পারে। Function পাঠাতে না পারলে উপায় কী? C-তে আছে function pointer — কাজ চলে, কিন্তু function তার চারপাশের variable মনে রাখতে পারে না; বাড়তি void * context পাঠাতে হয়। শুরুর Java-তে একটি interface বানিয়ে, একটি class লিখে, তার object পাঠাতে হতো — এক লাইনের নিয়মের জন্য পাঁচ-ছয় লাইন। এই ঝামেলার কারণে মানুষ এমন নকশা এড়িয়ে চলত।

JavaScript-এ একই কাজ items.sort((a, b) => a.price - b.price). যখন একটি কৌশল সস্তা হয়, মানুষ সেটি সব জায়গায় ব্যবহার করে। তাই JavaScript-এর জগতে তৈরি হয়েছে একটি “functional core”: event listener, timer, array method, Promise-এর then, middleware (Express-এর app.use(fn), পর্ব ৫০), React-এর component — সবই “আমাকে একটি function দাও, আমি ঠিক সময়ে ডাকব” নকশা। একে বলে inversion of control: কখন চলবে, তা ঠিক করে লাইব্রেরি; কী চলবে, তা ঠিক করেন আপনি।

তবে এর একটি দামও আছে। Function যখন সব জায়গায় ঘোরে, তখন “এই function কে, কখন, কোন this দিয়ে ডাকবে” বোঝা কঠিন হয় — এখান থেকেই callback hell (পর্ব ৩২) আর this-এর বিভ্রান্তি (পর্ব ২৪)। ভাষা পরে async/await আর arrow function দিয়ে এই কষ্ট কমিয়েছে।

মূল উপলব্ধি: First-class function আচরণকে ডেটার মতো পাঠানো যায় করে তোলে। এতে কোড ছোট আর নমনীয় হয়, কিন্তু নিয়ন্ত্রণের প্রবাহ চোখে কম দেখা যায় — তাই নাম আর ছোট function দিয়ে পাঠযোগ্যতা রক্ষা করাই আসল দক্ষতা।

প্র ০৩ES2015-এ arrow function আনা হলো কেন — শুধু ছোট করে লেখার জন্য, নাকি এর পেছনে বড় কোনো সমস্যা ছিল?

ছোট লেখা একটি কারণ, কিন্তু আসল কারণ ছিল this. ES2015-এর আগে সাধারণ function-এর ভেতরে this ঠিক হতো function কীভাবে ডাকা হলো তা দেখে। একটি object-এর method-এর ভেতরে setTimeout(function () { this.count++; }, 1000) লিখলে ভেতরের function-টি timer সাধারণ function হিসেবে ডাকে — তখন this আর object নয় — ব্রাউজারে global object window, Node.js-এ timer-এর নিজের Timeout object. প্রায় প্রতিটি JavaScript ডেভেলপার এই বাগে পড়েছেন।

সমাধান হিসেবে মানুষ লিখত var self = this; (বা that, _this) আর ভেতরে self.count++. ES5 (২০০৯) আনল function () { … }.bind(this). দুটোই কাজ করে, কিন্তু প্রতিটি callback-এ বাড়তি অনুষ্ঠান।

২০০৯ সালে Jeremy Ashkenas তৈরি করেন CoffeeScript — একটি ভাষা যা JavaScript-এ রূপান্তরিত হয়। এতে ছিল দুই রকম তীর: -> সাধারণ function, আর “fat arrow” => যা বাইরের this বেঁধে রাখে। CoffeeScript তখন বেশ জনপ্রিয় হয়, আর TC39 কমিটি দেখল মানুষ আসলে কী চায়। C#-এর lambda-তেও একই => চিহ্ন ছিল। ES2015 (ES6)-এ arrow function যুক্ত হলো একটি স্পষ্ট নকশা নিয়ে: এটি “হালকা” function — নিজের this, arguments, super বা new.target নেই, new দিয়ে ডাকা যায় না। সবকিছু নেয় বাইরের scope থেকে, ঠিক সাধারণ variable-এর মতো। একে বলে lexical this.

এই নকশার একটি পরিণাম আজকের সবচেয়ে সাধারণ ভুলগুলোর একটি: যেহেতু arrow সুন্দর আর ছোট, মানুষ (আর AI) একে object method-এও বসায়, আর তখন this object-কে দেখায় না। Arrow-কে “নতুন, ভালো function” না ভেবে “বাইরের প্রসঙ্গ ধরে রাখা function” ভাবলে ভুলটা আর হয় না।

মূল উপলব্ধি: Arrow function সাধারণ function-এর বদলি নয়, একটি বিশেষ যন্ত্র — callback-এ বাইরের প্রসঙ্গ ধরে রাখার জন্য। ভাষার নতুন বৈশিষ্ট্য প্রায়ই কোনো জনপ্রিয় “workaround” থেকে জন্মায়; workaround-টি জানলে বৈশিষ্ট্যটির সীমাও বোঝা যায়।

প্র ০৪অনেকে arguments object-কে JavaScript-এর একটি নকশাগত ভুল বলেন। কেন? Rest parameter কীভাবে সেই ভুল শোধরাল?

প্রথম JavaScript-এ যত খুশি argument নেওয়ার একমাত্র উপায় ছিল প্রতিটি function-এর ভেতরে স্বয়ংক্রিয়ভাবে তৈরি হওয়া arguments. দ্রুত তৈরি ভাষায় এটি সহজ সমাধান মনে হয়েছিল, কিন্তু কয়েকটি সমস্যা বহু বছর ধরে ভুগিয়েছে।

প্রথমত, এটি array নয়। length আর সংখ্যার index আছে, কিন্তু Array.prototype-এর method নেই। তাই একটি গোটা প্রজন্ম মুখস্থ করেছিল অদ্ভুত মন্ত্র Array.prototype.slice.call(arguments).

দ্বিতীয়ত, aliasing. সাধারণ (sloppy) mode-এ, parameter-গুলো সরল হলে, arguments[0] আর প্রথম parameter একই ঘরের দুই নাম। function f(a) { arguments[0] = 99; return a; } — f(1) দেয় ৯৯! একটি বদলালে অন্যটিও বদলায়। এটি পড়া কঠিন, আর engine-এর জন্যও কঠিন: এক ঘরের দুই নাম থাকলে optimization করা ঝামেলার। ES5-এর strict mode এই সংযোগ কেটে দেয় — সেখানে f(1) দেয় ১ — আর arguments.callee নিষিদ্ধ করে। Function-এ default বা rest parameter থাকলেও এই সংযোগ থাকে না।

তৃতীয়ত, অদৃশ্যতা। Function-এর signature দেখে বোঝা যায় না যে এটি বাড়তি argument পড়ে। আর arrow function-এ নিজস্ব arguments নেই — ভুল করে লিখলে বাইরের function-এর arguments চুপচাপ ধরে নেয়।

ES2015 সমাধান দিল rest parameter: function sum(...prices). এটি একটি আসল Array, নাম দেখেই উদ্দেশ্য বোঝা যায়, কোনো aliasing নেই, arrow-এও কাজ করে, আর শুধু “বাকি” argument নেয় — প্রথম কয়েকটি আলাদা নামে রাখা যায়। Engine-এর দিক থেকেও rest সহজ: কোডে arguments না থাকলে engine সেই বিশেষ object তৈরিই করে না।

মূল উপলব্ধি: একটি ভাষা থেকে কোনো বৈশিষ্ট্য সরানো প্রায় অসম্ভব — পুরোনো ওয়েবসাইট ভেঙে যাবে। তাই JavaScript ভুল মুছে ফেলে না; তার পাশে একটি ভালো বিকল্প যোগ করে (strict mode, rest, let), আর আমাদের কাজ নতুন কোডে বিকল্পটি বেছে নেওয়া।

প্র ০৫ES2015-এর মানদণ্ডে “proper tail call” আছে, তবুও Chrome বা Node.js-এ গভীর recursion stack ভরে ফেলে। মানদণ্ডে লেখা থাকলেও সব engine কেন এটি চালু করেনি?

প্রথমে ধারণাটি। একটি কল “tail position”-এ আছে যদি সেটিই function-এর শেষ কাজ হয় — ফল পেলেই সরাসরি ফেরত, আর কিছু করার বাকি নেই। return n * factorial(n - 1) tail call নয়, কারণ ফল পাওয়ার পরে গুণ করা বাকি। কিন্তু return fact(n - 1, acc * n) tail call. এমন ক্ষেত্রে বর্তমান frame-এর আর দরকার নেই, তাই engine চাইলে নতুন frame বসানোর বদলে পুরোনোটি পুনরায় ব্যবহার করতে পারে — তখন লাখ স্তরের recursion-ও একটি লুপের মতো কম মেমরিতে চলে। Scheme-এ এটি ভাষার বাধ্যতামূলক নিয়ম, আর Scheme-এর উত্তরাধিকার মনে রেখে ES2015 strict mode-এর জন্য “proper tail calls” মানদণ্ডে যোগ করে।

তারপর বাস্তবায়নের পালা। Apple-এর JavaScriptCore এটি চালু করে (Safari 10, ২০১৬ সাল থেকে) এবং এখনো রেখেছে। V8 দল এটি একটি flag-এর পেছনে বাস্তবায়ন করেছিল, কিন্তু পরে সরিয়ে নেয়; Mozilla-র SpiderMonkey কখনো চালু করেনি। তাদের আপত্তি ছিল মূলত দুটি: (১) frame মুছে গেলে stack trace থেকে মাঝের কলগুলো হারিয়ে যায়, debugging কঠিন হয়, আর error reporting টুলের ওপর নির্ভরশীল কোড ভাঙতে পারে; (২) এটি স্বয়ংক্রিয় ও অদৃশ্য — কোনো কল tail call কি না, লেখককে বোঝানোর উপায় নেই, একটি ছোট বদলে চুপচাপ সুবিধাটি চলে যায়। এর বিকল্প হিসেবে “syntactic tail calls” (যেমন return continue f(x) ধাঁচের স্পষ্ট চিহ্ন) প্রস্তাব করা হয়েছিল, কিন্তু সেটি TC39-এ এগোয়নি।

ফল একটি বিরল পরিস্থিতি: মানদণ্ডে আছে, কিন্তু বেশিরভাগ ব্যবহারকারীর ব্রাউজারে নেই। তাই বাস্তবে tail recursion-এর ওপর ভরসা করা যায় না। গভীর বা অজানা গভীরতার ডেটায় (লম্বা linked list, বিশাল নেস্টেড JSON) লুপ, নিজের বানানো stack (array) বা “trampoline” কৌশল ব্যবহার করাই নিরাপদ।

মূল উপলব্ধি: মানদণ্ড আর বাস্তবতা এক নয় — কোনো বৈশিষ্ট্য ব্যবহারের আগে দেখুন আপনার লক্ষ্য engine-গুলো এটি সত্যিই চালু করেছে কি না (MDN-এর compatibility টেবিল ভালো জায়গা)। আর ভাষার নকশায় “অদৃশ্য optimization” আর “debug করার সুবিধা”র মধ্যে টানাপোড়েন সবসময় থাকে।

অনুশীলন

এগুলোর বেশিরভাগে কোড লিখতে হবে না — আগে অনুমান করুন, তারপর console-এ চালিয়ে মিলিয়ে নিন।

  1. Default অনুমান করুন: function greet(name = "অতিথি") { return "স্বাগতম, " + name; } — এটি greet(), greet(undefined), greet(null) আর greet("") দিয়ে ডাকলে কী কী ফেরত আসবে? খাতায় লিখে তারপর চালান।

    প্রথম দুটিতে "স্বাগতম, অতিথি" — argument বাদ বা undefined, তাই default. greet(null) দেয় "স্বাগতম, null" — null default চালায় না, আর string-এর সাথে জোড়ার সময় “null” লেখা হয়ে যায়। greet("") দেয় "স্বাগতম, " — খালি string-ও একটি মান। খালি নাম আর null-কেও অতিথি বানাতে চাইলে body-তে লিখুন name = name || "অতিথি" (এখানে ০ বা false আসার ভয় নেই, তাই || চলে)।

  2. তিন রূপে লিখুন: “একটি সংখ্যা জোড় কি না” — এই কাজটি function declaration, function expression আর arrow function — তিনভাবে লিখুন। তারপর প্রতিটিকে ঘোষণার আগের লাইনে ডেকে দেখুন কোনটি কাজ করে আর কোনটি কী error দেয়।

    function isEven(n) { return n % 2 === 0; }, const isEven2 = function (n) { return n % 2 === 0; };, const isEven3 = (n) => n % 2 === 0;. ঘোষণার আগে ডাকলে শুধু declaration কাজ করে (পুরোটা hoist হয়)। বাকি দুটি const, তাই TDZ-এ পড়ে: ReferenceError: Cannot access 'isEven2' before initialization. var দিয়ে লিখলে আসত TypeError: isEven2 is not a function.

  3. বাগ খুঁজুন: একজন লিখেছেন button.addEventListener("click", saveForm()); — ক্লিক করার আগেই ফর্ম সেভ হয়ে যাচ্ছে, আর ক্লিক করলে কিছুই হয় না। কেন? আরেকজন লিখেছেন const toUser = (name) => { name: name }; আর toUser("Rafi") পাচ্ছেন undefined. কেন?

    প্রথমটিতে saveForm() — বন্ধনী থাকায় লাইনটি চলার সময়ই function ডাকা হয়ে গেছে, আর addEventListener পেয়েছে তার return value (সম্ভবত undefined). Function-টি পাঠাতে হবে বন্ধনী ছাড়া: addEventListener("click", saveForm). দ্বিতীয়টিতে =>-এর পরের { block body হিসেবে পড়া হয়েছে, name: হয়ে গেছে label, আর কোনো return নেই। সমাধান: (name) => ({ name: name }) বা সংক্ষেপে ({ name }).

  4. Pure না impure? নিচের প্রতিটি function pure কি না বলুন, আর impure হলে কোন side effect: (ক) (a, b) => a + b (খ) (list) => list.sort() (গ) (name) => console.log(name) (ঘ) (price) => price * Math.random() (ঙ) (list) => [...list].sort()

    (ক) pure. (খ) impure — sort মূল array-টিই বদলে দেয় (in-place); কলকারীর তালিকার ক্রম বদলে যায়। (গ) impure — console-এ লেখা একটি side effect (ক্ষতিকর নয়, কিন্তু বাইরের জগৎ বদলায়)। (ঘ) impure — একই input-এ প্রতিবার ভিন্ন output; টেস্ট করা কঠিন। (ঙ) pure — spread দিয়ে কপি বানিয়ে সেটি সাজায়, মূলটি অক্ষত। ES2023-এর list.toSorted()-ও একই কাজ করে (পর্ব ১৬)।

নিজেকে যাচাই করুন

ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।

ক্রমJavaScript-এ একটি function কল কোন ক্রমে ঘটে, সাজান (▲▼ চেপে বা টেনে)।

  1. 🧮 Argument-গুলোর মান হিসাব করা
  2. 📚 Call stack-এ নতুন frame বসা
  3. 📥 Parameter-এ মান বসা (দরকার হলে default)
  4. ⚙️ Body-র statement চলা
  5. 📤 return — frame সরে যায়, কলের জায়গায় ফল বসে

function f(x = 10) { return x; } — f(null) কী ফেরত দেয়?

  • 10
  • undefined
  • null
  • TypeError

const f = () => { total: 5 }; — f() কী ফেরত দেয়?

  • { total: 5 }
  • undefined
  • 5
  • SyntaxError

JavaScript-এ rest parameter (...args) সম্পর্কে কোন কথাটি সঠিক?

  • এটি array-এর মতো একটি object, তবে আসল array নয়
  • এটি parameter তালিকার যেকোনো জায়গায় বসানো যায়
  • Arrow function-এ rest ব্যবহার করা যায় না
  • এটি বাকি সব argument একটি আসল array-তে জড়ো করে, আর argument না এলে খালি array দেয়

ঘোষণার আগের লাইনে ডাকলে কোন function কোনো error ছাড়াই চলে?

  • function f() {}
  • const f = function () {};
  • const f = () => {};
  • var f = function () {};

একটি object-এর method হিসেবে getName: () => this.name লিখলে obj.getName() কেন object-এর name দেয় না?

  • Object-এর ভেতরে arrow function রাখা নিষিদ্ধ
  • Arrow function কিছু return করে না
  • Arrow function-এর নিজের this নেই — বাইরের scope-এর this নেয়
  • Method-এ this সবসময় আগে bind করতে হয়

[10, 9, 100].sort() JavaScript-এ কী দেয়?

  • [ 9, 10, 100 ]
  • [ 10, 100, 9 ]
  • [ 100, 10, 9 ]
  • [ 10, 9, 100 ]

শব্দকোষ

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

মনে রাখুন

  1. Function = নাম দেওয়া কাজ: parameter ঘরের নাম, argument ঘরে রাখা মান, return ফল নিয়ে বেরোয়; return না থাকলে undefined.
  2. Declaration পুরোটা hoist হয়; expression আর arrow variable-এর নিয়ম মানে। Arrow-এ object ফেরত দিতে () => ({ … }).
  3. Default খাটে শুধু undefined-এ, null-এ নয়; rest ...args আসল array, পুরোনো arguments-এর বদলি।
  4. Function একটি মান: পাঠান বন্ধনী ছাড়া (fn), ডাকুন বন্ধনী দিয়ে (fn())। Callback, higher-order function আর closure এখান থেকেই।
  5. Arrow-এর নিজের this নেই — callback-এ ভালো, method-এ নয়। Function ছোট, pure আর এক-কাজের রাখুন; recursion-এ base case ভুলবেন না।

পরের পর্বে: মডিউল ২ শেষ — মান, variable, অপারেটর, লুপ আর function দিয়ে আপনি এখন যেকোনো হিসাব লিখতে পারেন। মডিউল ৩ শুরু হবে object দিয়ে: একটি দোকানের পণ্যের নাম, দাম আর স্টক একসাথে কীভাবে রাখবেন, object-এর ভেতরে function রাখলে কীভাবে method হয়, আর দুটি variable একই object দেখালে কেন একটির বদল অন্যটিতেও দেখা যায়।

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

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