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

মান, টাইপ আর coercion-এর ফাঁদ

Values, Types & Coercion — typeof, null vs undefined, Conversions, == vs === and the Famous Puzzles

Taught in Bangla, this lesson explains JavaScript values and types: the seven primitives plus object, typeof and its quirks, null versus undefined, primitives versus references, explicit conversion with Number, String, Boolean and parseInt, the rules of implicit coercion, loose versus strict equality, and solving famous puzzles like [] + {} by applying the rules.

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

  • JavaScript-এর ৮টি টাইপ, primitive আর object-এর মৌলিক পার্থক্য
  • typeof কীভাবে সিদ্ধান্ত নেয়, তার খামখেয়ালি, আর টাইপ নিশ্চিতভাবে যাচাইয়ের উপায়
  • undefined আর null — কোনটি কখন, আর API ডেটায় এদের পার্থক্য
  • নিজে হাতে রূপান্তর (Number, String, parseInt…) আর নিজে থেকে রূপান্তরের (coercion) নিয়ম
  • == বনাম ===, NaN, Object.is — আর [] + {} জাতীয় ধাঁধা নিয়ম দিয়ে ব্যাখ্যা করা

১ · টাইপ থাকে মানের, variable-এর নয়

মডিউল ১-এ দেখেছেন engine কীভাবে কোড চালায়। এবার ভাষার একদম ভেতরে ঢুকি। প্রোগ্রাম যা নিয়ে কাজ করে, তা হলো মান (value) — 42, "ঢাকা", true. প্রতিটি মানের একটি টাইপ আছে: এটি সংখ্যা, এটি লেখা, এটি হ্যাঁ/না। টাইপ ঠিক করে মানটি দিয়ে কী কী করা যায় — সংখ্যা গুণ করা যায়, লেখাকে বড় হাতের অক্ষরে বদলানো যায়।

C বা Java-তে variable ঘোষণার সময়ই টাইপ বলে দিতে হয়: int age = 20;. এরপর age-এ কখনো লেখা রাখা যাবে না — compiler আগেই আটকে দেবে। একে বলে static typing. JavaScript-এ নিয়ম আলাদা:

let box = 42;          // এখন box-এ একটি number
box = "বিয়াল্লিশ";      // এখন string — কোনো error নেই
box = true;            // এখন boolean
box = null;            // এখন null

এই কোড পুরোপুরি বৈধ। box নিজে কোনো টাইপ “জানে” না — সে শুধু একটি নাম, যা যেকোনো মানের দিকে দেখাতে পারে। টাইপ আছে মানগুলোর গায়ে: 42 number, "বিয়াল্লিশ" string. একে বলে dynamic typing — টাইপ যাচাই হয় কোড চলার সময়, আগে নয়। (let নিয়ে বিস্তারিত পর্ব ০৭-এ।)

রেলস্টেশনের কুলির কথা ভাবুন। কুলি নিজে কোনো নির্দিষ্ট মালের জন্য নন — এখন তাঁর মাথায় চালের বস্তা, একটু পরে স্যুটকেস, তারপর ফলের ঝুড়ি। “এটা ভঙ্গুর”, “এটা ভারী” — এই লেবেল থাকে মালের গায়ে, কুলির গায়ে নয়। JavaScript-এর variable ওই কুলির মতো; টাইপের লেবেল থাকে মানের গায়ে।

এর একটি দ্বিতীয় দিক আছে, যা এই পর্বের মূল বিষয়। কোনো কাজে যদি “ভুল” টাইপের মান আসে — যেমন লেখা থেকে সংখ্যা বিয়োগ — অনেক ভাষা সাথে সাথে error দেয়। Python-এ "5" - 3 লিখলে TypeError. JavaScript সাধারণত error দেয় না; সে চুপচাপ মানটিকে অন্য টাইপে বদলে নিয়ে কাজ চালিয়ে যায়। এই স্বয়ংক্রিয় বদলকে বলে type coercion.

মূল ধারণা

Coercion এলোমেলো নয় — প্রতিটি রূপান্তরের নির্দিষ্ট নিয়ম ECMAScript-এ লেখা আছে। নিয়মগুলো না জানলে ফলাফল “অদ্ভুত” মনে হয়; জানলে আগেই বলে দেওয়া যায় কী হবে। এই পর্বের লক্ষ্য হলো চমককে পূর্বানুমেয় আচরণে বদলে দেওয়া।

২ · আটটি টাইপ — সাতটি primitive আর object

ECMAScript ঠিক ৮টি টাইপ নির্ধারণ করেছে। প্রথম সাতটিকে বলে primitive — একক, ভাঙা যায় না এমন মান। অষ্টমটি object — অনেক মানের একটি সংগ্রহ। Array, function, তারিখ — সবই আসলে object.

টাইপউদাহরণকী বোঝায়
string"ঢাকা", '01712', `মোট ${n}`লেখা — অক্ষরের সারি (পর্ব ০৯)
number42, 3.14, -0, Infinity, NaNসব সংখ্যা — পূর্ণ ও ভগ্নাংশ একই টাইপে (পর্ব ১০)
bigint9007199254740993nযত বড় খুশি পূর্ণসংখ্যা — ES2020-এ যুক্ত
booleantrue, falseহ্যাঁ বা না — শুধু দুটি মান
undefinedundefined“এখনো কোনো মান দেওয়া হয়নি”
nullnull“ইচ্ছা করে খালি রাখা হয়েছে”
symbolSymbol("id")প্রতিবার অনন্য একটি চিহ্ন — ES2015-এ যুক্ত (পর্ব ২৮)
object{ name: "Rafi" }, [1, 2], function(){}নাম-মানের জোড়ার সংগ্রহ; বাকি সব কিছু (পর্ব ১৪)

কয়েকটি বিষয় খেয়াল করুন। প্রথমত, JavaScript-এ int আর float আলাদা নয় — 5 আর 5.0 একই number. সব number আসলে ৬৪-বিটের floating-point, তাই 0.1 + 0.2 হয় 0.30000000000000004 (কেন — পর্ব ১০-এ)। দ্বিতীয়ত, NaN (“Not a Number”) নিজেই একটি number — “সংখ্যা হওয়ার কথা ছিল, কিন্তু হিসাব ব্যর্থ” বোঝায়। তৃতীয়ত, “খালি” বোঝানোর জন্য JavaScript-এ একটি নয়, দুটি মান আছে।

undefined বনাম null — অনুপস্থিত বনাম ইচ্ছাকৃত খালি

undefined সাধারণত engine নিজে বসায়: মান না দিয়ে ঘোষিত variable, না-পাঠানো argument, object-এ নেই এমন property, কিছু return না করা function — সবই undefined. অর্থাৎ “এখানে কিছু থাকার কথা, কিন্তু কেউ কিছু রাখেনি”।

null engine প্রায় কখনো নিজে বসায় না — প্রোগ্রামার লেখেন। এর অর্থ “আমি জেনেশুনে বলছি, এখানে কোনো মান নেই”। বিয়ের দাওয়াতের তালিকার কথা ভাবুন: কারো নামের পাশে ঘর ফাঁকা থাকলে বোঝা যায় না তিনি আসবেন কি না — উত্তরই আসেনি (undefined). কিন্তু “আসবেন না” লেখা থাকলে সেটি একটি পরিষ্কার উত্তর (null).

[
  { "name": "Rafi", "phone": "01712345678" },
  { "name": "Mim",  "phone": null },
  { "name": "Tanim" }
]

একটি API থেকে এমন ডেটা আসা খুবই সাধারণ। Mim-এর ক্ষেত্রে সার্ভার স্পষ্ট বলছে “ফোন নম্বর নেই” — user.phone হবে null. Tanim-এর ক্ষেত্রে phone ঘরটাই নেই — user.phone পড়লে পাবেন undefined. দুটির অর্থ আলাদা হতে পারে: হয়তো Mim নম্বর মুছে ফেলেছেন, আর Tanim-এর ডেটা পুরোনো version-এর API থেকে এসেছে। JSON-এ null আছে, কিন্তু undefined নেই — JSON.stringify({ a: undefined, b: null }) দেয় {"b":null}; undefined-ওয়ালা ঘর নিঃশব্দে বাদ পড়ে (পর্ব ১৯)।

null বা undefined-এর ওপর property পড়তে গেলে প্রোগ্রাম থেমে যায়। V8-এর বার্তা (Chrome, Edge, Node.js): TypeError: Cannot read properties of null (reading 'x'). JavaScript-এর সবচেয়ে বেশি দেখা TypeError-গুলোর একটি এটি। user?.phone (optional chaining) আর ?? দিয়ে নিরাপদে সামলানো শিখবেন পর্ব ০৮-এ।

৩ · typeof — টাইপ জিজ্ঞেস করা, আর তার খামখেয়ালি

কোনো মানের টাইপ জানতে আছে typeof অপারেটর। এটি সবসময় একটি string ফেরত দেয়: typeof 42 → "number", typeof "hi" → "string". কিন্তু টাইপ ৮টি হলেও typeof-এর উত্তরের তালিকা হুবহু সেই ৮টি নয়। নিচের ডায়াগ্রামে দেখুন typeof কীভাবে সিদ্ধান্ত নেয়:

  1. null?"object"ঐতিহাসিক বাগ
  2. অন্য primitive?নিজের নাম৬টি উত্তর
  3. ডাকা যায়?"function"function, class
  4. বাকি সব"object"{}, array, Date…
প্রশ্ন ১মানটি কি null?
typeof null          // "object"  ← ভুল, কিন্তু স্থায়ী
// null একটি primitive, তবু উত্তর "object"
প্রশ্ন ২বাকি ৬টি primitive — নিজের নামই উত্তর
typeof "ঢাকা"        // "string"
typeof 42            // "number"
typeof 10n           // "bigint"
typeof false         // "boolean"
typeof undefined     // "undefined"
typeof Symbol()      // "symbol"
প্রশ্ন ৩object, কিন্তু ডাকা (call) যায়?
typeof function(){}  // "function"
typeof (() => 1)     // "function"
typeof class {}      // "function"
// function আলাদা টাইপ নয় — ডাকা যায় এমন object
প্রশ্ন ৪বাকি সব object
typeof {}            // "object"
typeof [1, 2]        // "object"  ← "array" নয়!
typeof new Date(0)   // "object"
typeof new Map()     // "object"
আরও দুটি ফাঁকিউত্তর ঠিক, কিন্তু চমকপ্রদ
typeof NaN           // "number"
typeof notDeclared   // "undefined" — ReferenceError নয়
// তাই নিশ্চিত যাচাইয়ে আলাদা টুল লাগে (নিচের টেবিল)
  1. প্রশ্ন ১ — null? মানটি null হলে typeof বলে "object". এটি ১৯৯৫ সালের প্রথম বাস্তবায়নের একটি বাগ, যা আর ঠিক করা যায়নি (ভাবনার প্রশ্ন ২)। তাই null যাচাই করতে typeof নয়, সরাসরি x === null লিখুন।
  2. প্রশ্ন ২ — বাকি primitive. string, number, bigint, boolean, undefined, symbol — এই ৬টির ক্ষেত্রে typeof টাইপের নামটাই ফেরত দেয়। এখানে কোনো চমক নেই।
  3. প্রশ্ন ৩ — ডাকা যায়? Object হলে typeof দেখে সেটি function-এর মতো ডাকা যায় কি না। যায় হলে উত্তর "function" — যদিও function আলাদা টাইপ নয়, একটি বিশেষ object. Class-ও আসলে function, তাই সেটিও "function".
  4. প্রশ্ন ৪ — বাকি সব. সাধারণ object, array, তারিখ, Map — সবকিছুর উত্তর "object". খেয়াল করুন, array-র জন্যও "object"; typeof x === "array" কখনো সত্য হয় না।
  5. আরও দুটি ফাঁকি. NaN একটি number, তাই "number". আর ঘোষণাই হয়নি এমন নামে typeof চালালে error না দিয়ে "undefined" দেয় — পুরোনো কোডে কোনো global আছে কি না দেখার জন্য এটি ব্যবহার হতো। (তবে let দিয়ে পরে ঘোষিত নামে আগে typeof চালালে error আসে — TDZ, পর্ব ০৭।)

নিশ্চিতভাবে টাইপ যাচাই — কোন কাজে কোন টুল

যা জানতে চানযা লিখবেনযা এড়াবেন
string কি নাtypeof x === "string"—
সত্যিকারের, সসীম সংখ্যা কি নাNumber.isFinite(x)typeof x === "number" (NaN, Infinity-ও পাস করে)
array কি নাArray.isArray(x)typeof x === "array" (সবসময় false)
null কি নাx === nulltypeof x === "object"
null বা undefined — যেকোনো একটিx == null!x (0, "" -ও ধরে ফেলে)
NaN কি নাNumber.isNaN(x)x === NaN (সবসময় false)

৪ · Primitive বনাম object — মান বনাম reference

Primitive আর object-এর পার্থক্য শুধু তালিকার নয় — আচরণেও। দুটি বড় পার্থক্য আছে।

🔹 Primitive — অপরিবর্তনীয় মান

Primitive মান immutable: একবার তৈরি হলে আর বদলায় না। "abc".toUpperCase() মূল string বদলায় না, নতুন একটি string "ABC" বানায়। variable-এ নতুন মান বসানো যায়, কিন্তু মানটিকে ভেতর থেকে বদলানো যায় না।

কপি করলে মানটাই কপি হয়

📦 Object — বদলানো যায়, ভাগ হয়

Object থাকে heap-এ; variable-এ থাকে তার reference (ঠিকানা)। একটি object দুই variable-এ রাখলে দুটিই একই object দেখায় — একটি দিয়ে বদলালে অন্যটিতেও বদল দেখা যায়।

কপি করলে ঠিকানা কপি হয় (পর্ব ১৪)
let a = 5;
let b = a;       // মান 5 কপি হলো
b = b + 1;       // a এখনো 5, b হলো 6

let s = "abc";
s[0] = "X";      // কিছুই হয় না — string বদলানো যায় না
// s এখনো "abc"

const p = { score: 5 };
const q = p;     // একই object-এর ঠিকানা
q.score = 6;     // p.score-ও এখন 6

প্রথম অংশে b পেয়েছে 5-এর একটি আলাদা কপি, তাই a অপরিবর্তিত। দ্বিতীয় অংশে s[0] = "X" সাধারণ script-এ নিঃশব্দে ব্যর্থ হয়; strict mode-এ (পর্ব ০৫) V8 error দেয়: TypeError: Cannot assign to read only property '0' of string 'abc'. তৃতীয় অংশে p আর q একই object-এর দুটি নাম — বাসার একটি ঠিকানা দুজনের কাছে লেখা থাকার মতো। Object আর reference নিয়ে পুরো আলোচনা পর্ব ১৪-এ।

একটি প্রশ্ন জাগতে পারে: primitive যদি object না হয়, তাহলে "abc".length বা "abc".toUpperCase() কাজ করে কীভাবে? Property তো object-এর থাকে! উত্তর: engine মুহূর্তের জন্য string-টিকে একটি String wrapper object-এ মুড়ে নেয়, method চালায়, তারপর wrapper ফেলে দেয়। একে বলে autoboxing. এজন্য primitive-কেও object-এর মতো ব্যবহার করা যায়, অথচ সেগুলো হালকা আর অপরিবর্তনীয় থাকে। নিজে কখনো new String("a") লিখবেন না — এটি সত্যিকারের object বানায়, আর new String("a") === "a" হয় false.

৫ · নিজে হাতে রূপান্তর — Number, String, Boolean, parseInt

Coercion-এর নিয়ম শেখার আগে দেখি সবচেয়ে নিরাপদ পথ: নিজে স্পষ্ট করে রূপান্তর (explicit conversion). এতে কোড পড়েই বোঝা যায় কী হচ্ছে।

মানNumber(x)String(x)Boolean(x)
"" (খালি)0 ⚠️""false
" 42 "42" 42 "true
"42px"NaN"42px"true
"0"0"0"true ⚠️
null0"null"false
undefinedNaN"undefined"false
true1"true"true
[]0""true ⚠️
[5]5"5"true
{}NaN"[object Object]"true

⚠️ চিহ্নগুলোতে মনোযোগ দিন। খালি string থেকে Number দেয় 0, NaN নয় — তাই form-এর খালি ঘর চুপচাপ শূন্য হয়ে যেতে পারে। "0" লেখাটি boolean-এ true, কারণ খালি নয় এমন সব string true. খালি array-ও true, কারণ সব object true.

parseInt আর parseFloat — শুরু থেকে যতটুকু পড়া যায়

Number() কড়া: পুরো string সংখ্যা না হলে NaN. parseInt আর parseFloat নমনীয়: শুরু থেকে যতক্ষণ সংখ্যা পায় পড়ে, বাকিটা ফেলে দেয়। parseInt("42px", 10) → 42, parseFloat("3.5kg") → 3.5. CSS-এর "16px" থেকে সংখ্যা বের করতে এটি কাজের।

parseInt-এর দ্বিতীয় argument হলো radix — কোন ভিত্তির সংখ্যা: parseInt("101", 2) → 5 (binary), parseInt("ff", 16) → 255. Radix না দিলে "0x" দিয়ে শুরু হওয়া string-কে hexadecimal ধরে। পুরোনো engine-এ "08"-কে octal ধরার সমস্যাও ছিল, যা ES5 বন্ধ করে। তাই নিয়ম: সবসময় radix দিন, সাধারণত 10.

আরেকটি ছোট পথ হলো unary plus: +"42" → 42. এটি প্রায় হুবহু Number()-এর মতো কাজ করে, শুধু ছোট (ব্যতিক্রম BigInt: Number(10n) দেয় 10, কিন্তু +10n দেয় TypeError). তবে নতুনদের জন্য Number(x) পড়তে বেশি পরিষ্কার।

এবার একটি বাস্তব বাগ দেখুন। একটি অনলাইন দোকানের form-এ দাম আর ডেলিভারি চার্জ লেখা থাকে। HTML input-এর value সবসময় string — এমনকি type="number" হলেও (পর্ব ৩৯)।

// form থেকে আসা মান — input.value সবসময় string
const priceInput = "500";
const deliveryInput = "50";

console.log("ভুল মোট:", priceInput + deliveryInput);
console.log("ঠিক মোট:", Number(priceInput) + Number(deliveryInput));

// রূপান্তরের নিয়ম
console.log(Number(""), Number(" 42 "), Number("42px"));
console.log(parseInt("42px", 10), parseFloat("3.5kg"));
console.log(Number(null), Number(undefined), Number(true));
console.log(String(null), String([1, 2]), String({}));
console.log(Boolean(""), Boolean("0"), Boolean([]), Boolean(0));

// ফোন নম্বর: number বানালে শুরুর 0 হারায়
const phone = "01712345678";
console.log(parseInt(phone, 10), Number(phone));
console.log(typeof phone, phone.length);

প্রথম লাইনে আসে ভুল মোট: 50050 — দুটি string জোড়া লেগেছে, যোগ হয়নি। ৫৫০ টাকার জায়গায় গ্রাহক দেখবেন পঞ্চাশ হাজার পঞ্চাশ! দ্বিতীয় লাইনে আগে Number দিয়ে বদলে নেওয়ায় ঠিক মোট: 550. এরপরের লাইনগুলো টেবিলের নিয়মগুলো মিলিয়ে দেখায়: 0 42 NaN, 42 3.5, 0 NaN 1, null 1,2 [object Object], false true true false. শেষ অংশে ফোন নম্বর number বানালে আসে 1712345678 — শুরুর 0 হারিয়ে গেছে, কারণ সংখ্যার কাছে সামনের শূন্যের কোনো অর্থ নেই। string হিসেবে রাখলে সব ঠিক: string 11.

মূল ধারণা

যা দিয়ে হিসাব করবেন না, তা number বানাবেন না। ফোন নম্বর, রোল নম্বর, পোস্ট কোড, NID, অর্ডার আইডি — এগুলো দেখতে সংখ্যার মতো হলেও আসলে লেবেল। এদের যোগ-বিয়োগ হয় না, কিন্তু সামনের শূন্য বা দৈর্ঘ্য গুরুত্বপূর্ণ। এগুলো string হিসেবে রাখুন। খুব বড় আইডি number বানালে আরও বিপদ: Number("9007199254740993") দেয় 9007199254740992 — শেষ অঙ্কটাই বদলে যায় (পর্ব ১০)।

৬ · নিজে থেকে রূপান্তর — coercion-এর চার নিয়ম

আপনি রূপান্তর না লিখলেও অপারেটর যখন ভিন্ন টাইপ চায়, engine নিজে রূপান্তর করে। ECMAScript-এ এর জন্য কয়েকটি ভেতরের “abstract operation” আছে। সরল করে চারটি জানলেই বেশিরভাগ ক্ষেত্র বোঝা যায়:

নিয়মকখন চলেমূল কথা
ToPrimitiveobject-কে গাণিতিক বা তুলনার কাজে লাগলেobject থেকে একটি primitive বের করা: আগে valueOf(), তাতে primitive না এলে toString(). [] → "", [5] → "5", {} → "[object Object]"
ToNumber- * / %, unary +, < >, আর ==-এর কিছু ধাপউপরের টেবিলের Number(x) কলামের নিয়ম
ToString+-এর এক পাশে string থাকলে, template literal-এString(x) কলামের নিয়ম
ToBooleanif, while, !, &&, ||শুধু ৮টি মান false: false, 0, -0, 0n, "", null, undefined, NaN. বাকি সব true

ToBoolean-এর ওই ৮টি মানকে বলে falsy, বাকি সবকে truthy — এর ব্যবহার আর ফাঁদ পর্ব ০৮-এ বিস্তারিত দেখবেন।

+ অপারেটর — দুই কাজের এক চিহ্ন

বেশিরভাগ চমকের কেন্দ্রে আছে +. অন্য গাণিতিক অপারেটরের একটাই কাজ — সংখ্যার হিসাব। কিন্তু + দুটি কাজ করে: সংখ্যা যোগ আর string জোড়া লাগানো (concatenation). কোনটা করবে, তা ঠিক হয় এক সরল নিয়মে: দুই পাশকে primitive বানানোর পর যেকোনো এক পাশ string হলে, string জেতে। ধাপে ধাপে দেখুন:

দুটি expression পাশাপাশি
"5" + 3
"5" - 3
শিক্ষা
  1. দুই পাশের মান. দুটি expression-এ একই দুটি মান: string "5" আর number 3. পার্থক্য শুধু অপারেটরে।
  2. ধাপ ১ — ToPrimitive. কোনো পাশে object থাকলে আগে সেটিকে primitive বানানো হতো। এখানে দুটোই primitive, তাই এই ধাপে কিছু বদলায় না।
  3. ধাপ ২ — অপারেটরের পছন্দ. + দেখে কোনো পাশ string কি না — আছে, তাই অন্য পাশকে ToString করে "3". --এর জন্য string জোড়ার কোনো অর্থ নেই, তাই সে দুই পাশকেই ToNumber করে; "5" হয় 5.
  4. ধাপ ৩ — আসল কাজ. + দুটি string জোড়া লাগায়; - সংখ্যার বিয়োগ করে। *, /, %, ** — সবাই --এর মতো আচরণ করে।
  5. ফল ও শিক্ষা. "53" বনাম 2. দোকানের "500" + 50 এজন্যই "50050". যেখানে মান string হতে পারে (form, URL, ফাইল, API), সেখানে যোগের আগে নিজে Number() দিয়ে বদলে নিন।

বাম থেকে ডানে হিসাব হয় বলে ক্রমও গুরুত্বপূর্ণ: 1 + 2 + "3" → আগে 1 + 2 = 3, তারপর 3 + "3" → "33". কিন্তু "1" + 2 + 3 → "12", তারপর "123". Template literal (`মোট: ${a + b}`) ব্যবহার করলে কোন অংশ লেখা আর কোন অংশ হিসাব, তা পরিষ্কার থাকে (পর্ব ০৯)।

সব টাইপ coerce হয় না। দুটি নতুন primitive ইচ্ছা করেই কড়া: 1n + 1 দেয় TypeError: Cannot mix BigInt and other types, use explicit conversions, আর Symbol() + "" দেয় TypeError: Cannot convert a Symbol value to a string (দুটিই V8-এর বার্তা)। ভাষার নকশাকারীরা পুরোনো ভুল থেকে শিখেছেন — নতুন টাইপে চুপচাপ রূপান্তরের বদলে স্পষ্ট error.

৭ · == বনাম === — ঢিলেঢালা আর কড়া সমতা

JavaScript-এ সমতা যাচাইয়ের দুটি অপারেটর। strict equality === কখনো রূপান্তর করে না: টাইপ আলাদা হলেই false. loose equality == টাইপ আলাদা হলে আগে coercion করে, তারপর তুলনা করে। ==-এর নিয়ম (সরল রূপে):

  1. টাইপ একই হলে → ===-এর মতোই তুলনা।
  2. null আর undefined একে অপরের সমান; অন্য কোনো কিছুর সমান নয় — এখানে কোনো রূপান্তর হয় না।
  3. number বনাম string → string-কে number বানাও।
  4. কোনো পাশে boolean থাকলে → আগে সেটিকে number বানাও (true → 1, false → 0).
  5. object বনাম primitive → object-কে ToPrimitive করো, তারপর আবার শুরু থেকে।
তিনটি তুলনা, একই নিয়মের তালিকা ধরে
0 == ''
null == undefined
null == 0
  1. নিয়ম ১ — টাইপ কি একই? তিনটি তুলনাতেই দুই পাশের টাইপ আলাদা, তাই সরাসরি তুলনা হবে না — পরের নিয়মে যেতে হবে।
  2. নিয়ম ২ — null/undefined. দ্বিতীয় তুলনায় দুটোই এই জোড়ার সদস্য, তাই সাথে সাথে true. তৃতীয়টিতে এক পাশে null, অন্য পাশে number — null শুধু undefined-এর সমান, তাই সাথে সাথে false. প্রথমটিতে null/undefined নেই, এগিয়ে চলুন।
  3. নিয়ম ৩ — number বনাম string. 0 == ''-এ string-কে ToNumber করা হলো: খালি string → 0. খেয়াল করুন, null == 0-এ null-কে কখনো number বানানো হয়নি — নিয়ম ২ আগেই উত্তর দিয়ে দিয়েছে।
  4. আবার নিয়ম ১. এখন 0 == 0 — টাইপ একই, সাধারণ তুলনা।
  5. ফল. true, true, false. মজার বিষয়: null >= 0 কিন্তু true, কারণ >= সরাসরি ToNumber চালায় (null → 0), আর == চালায় না। একই মানে দুই অপারেটর দুই নিয়মে চলে — এজন্যই == মনে রাখা কঠিন।

এই নিয়মগুলো থেকে আসে কুখ্যাত ফলাফল: "" == 0 true, "0" == 0 true, অথচ "" == "0" false (দুটোই string, তাই সরাসরি তুলনা)। অর্থাৎ == transitive-ও নয়। [] == false true, আবার [] নিজে truthy. এমনকি [] == ![]-ও true!

ডিফল্ট: সবসময় === আর !==
একমাত্র প্রচলিত ব্যতিক্রম: x == null

ব্যতিক্রমটি কেন? নিয়ম ২ অনুযায়ী x == null সত্য হয় শুধু যখন x null বা undefined — আর কোনো মানে নয় (0, "", false নয়)। তাই “মানটি আছে কি না” দেখতে x === null || x === undefined-এর ছোট ও নির্ভরযোগ্য রূপ এটি। অনেক দল ESLint-এর eqeqeq নিয়ম চালু করে == নিষেধ করে, শুধু এই একটি ব্যবহারের অনুমতি রেখে (পর্ব ৪৬)।

NaN — নিজের সমানও নয়

NaN একমাত্র মান যা নিজের সমান নয়: NaN === NaN → false. এটি JavaScript-এর খামখেয়ালি নয় — floating-point-এর আন্তর্জাতিক মানদণ্ড IEEE 754 এভাবেই বলে, C, Java, Python-এও একই। যুক্তি: দুটি ব্যর্থ হিসাবের ফল “একই” বলা যায় না। তাই NaN খুঁজতে === NaN কাজ করে না।

  • isNaN(x) (পুরোনো, global) আগে x-কে number বানায়, তারপর দেখে। তাই isNaN("abc") → true, যদিও "abc" NaN নয়, একটি string.
  • Number.isNaN(x) (ES2015) কোনো রূপান্তর করে না: শুধু সত্যিকারের NaN-এর জন্য true. Number.isNaN("abc") → false. এটিই ব্যবহার করুন।

Object.is — তৃতীয় সমতা

ES2015 আরেকটি তুলনা যোগ করে: Object.is(a, b). এটি প্রায় ===-এর মতো, শুধু দুটি কোণার ক্ষেত্রে আলাদা: Object.is(NaN, NaN) → true, আর Object.is(0, -0) → false (যেখানে 0 === -0 true). প্রতিদিনের কোডে কমই লাগে, কিন্তু React-এর মতো লাইব্রেরি state বদলেছে কি না দেখতে এটি ব্যবহার করে।

৮ · বিখ্যাত ধাঁধা — নিয়ম দিয়ে খুলে দেখা

ইন্টারনেটে “JavaScript পাগল ভাষা” প্রমাণে কিছু উদাহরণ ঘুরে বেড়ায়। এখন আপনার কাছে নিয়ম আছে — প্রতিটি খুলে দেখা যায়।

  • [] + [] → "": দুটি array ToPrimitive হয়ে "" আর ""; string জেতে, জোড়া লেগে খালি string.
  • [] + {} → "[object Object]": "" + "[object Object]".
  • true + true → 2: কোনো পাশে string নেই, তাই দুটোই ToNumber → 1 + 1.
  • 0.1 + 0.2 → 0.30000000000000004: এটি coercion নয়, binary floating-point-এর সীমা (পর্ব ১০)।
  • {} + [] → প্রেক্ষাপটের ওপর নির্ভর করে! নিচে দেখুন।

সবচেয়ে মজারটি হলো ("b" + "a" + +"a" + "a").toLowerCase(), যা দেয় "banana". কীভাবে?

বাম থেকে ডানে হিসাব
এই ধাপ
ফল
  1. Parser-এর চোখে. + +"a"-এ পরপর দুটি +. প্রথমটি যোগ, দ্বিতীয়টি unary plus — যা শুধু তার ডানের মানকে number বানায়। Unary অপারেটর আগে বাঁধে, তাই expression দাঁড়ায় ("b" + "a") + (+"a") + "a".
  2. প্রথম যোগ. দুটি string — জোড়া লেগে "ba".
  3. Unary plus. +"a" মানে ToNumber("a"). "a" কোনো সংখ্যা নয়, তাই ফল NaN.
  4. string জেতে. "ba" + NaN — এক পাশে string, তাই NaN-কে ToString করা হলো: "NaN". ফল "baNaN".
  5. শেষ যোগ. "baNaN" + "a" → "baNaNa". ছোট হাতের অক্ষরে বদলালে "banana". কোনো জাদু নেই — চারটি নিয়ম পরপর।

{} + [] — console-এ এক, কোডে আরেক

লাইনের শুরুতে { থাকলে parser সেটিকে object নয়, একটি খালি block (যেমন if-এর { }) হিসেবে পড়ে। তখন বাকি থাকে +[] — unary plus, ফল 0. কিন্তু {} + [] যদি কোনো expression-এর ভেতরে থাকে (const x = {} + []; বা বন্ধনীতে), {} হয় object, আর ফল "[object Object]".

এখানে টুলের পার্থক্যও আছে। Chrome/Edge DevTools-এর console আর Node.js-এর REPL — দুটিই আজকাল { দিয়ে শুরু হওয়া ইনপুটকে আগে বন্ধনীতে মুড়ে expression হিসেবে চেষ্টা করে, তাই সেখানে {} + [] লিখলে দেখায় '[object Object]'. কিন্তু eval("{} + []") বা script-এর একটি statement হিসেবে এটি block-এর নিয়মে চলে, ফল 0. ২০১২ সালের বিখ্যাত “Wat” বক্তৃতায় যে 0 দেখানো হয়েছিল, তা এই statement-প্রেক্ষাপট থেকেই। নিচে চালিয়ে মিলিয়ে নিন:

const fmt = v => typeof v === "string" ? `"${v}"` : String(v);
const show = (code, v) => console.log(code.padEnd(20), "→", fmt(v));

show('"5" + 3', "5" + 3);
show('"5" - 3', "5" - 3);
show("[] + []", [] + []);
show("[] + {}", [] + {});
show("true + true", true + true);
show('"b"+"a"+ +"a"+"a"', "b" + "a" + +"a" + "a");
show("eval('{} + []')", eval("{} + []"));
show("({} + [])", ({} + []));
show("0 == ''", 0 == "");
show("null == 0", null == 0);
show("NaN === NaN", NaN === NaN);
show("Object.is(NaN, NaN)", Object.is(NaN, NaN));
show("typeof null", typeof null);

প্রথম দুই লাইনে ছোট সাহায্যকারী function: fmt string হলে উদ্ধৃতিচিহ্নসহ দেখায়, যাতে "53" (string) আর 2 (number) আলাদা চেনা যায়; খালি string দেখায় "". আউটপুট: "53", 2, "", "[object Object]", 2, "baNaNa", 0, "[object Object]", true, false, false, true, "object". eval এখানে শুধু statement-প্রেক্ষাপট দেখানোর জন্য; বাস্তব কোডে eval ব্যবহার করবেন না — এটি ধীর আর নিরাপত্তার ঝুঁকি তৈরি করে (পর্ব ৪৩)।

নিয়ম জানাই কি যথেষ্ট? — TypeScript কেন এল

নিয়ম জানলে ধাঁধা বোঝা যায়, কিন্তু বড় প্রজেক্টে হাজার হাজার লাইনে প্রতিটি মানের টাইপ মাথায় রাখা অসম্ভব। price + delivery লাইনটি দেখে বোঝা যায় না দুটিই number কি না। এই সমস্যার সমাধানে Microsoft ২০১২ সালে আনে TypeScript — JavaScript-এর ওপর টাইপের একটি স্তর, যা কোড চালানোর আগেই বলে দেয় “এখানে string আর number যোগ হচ্ছে”। চালানোর সময় টাইপ মুছে সাধারণ JavaScript হয়ে যায়, তাই এই পর্বের সব নিয়ম তখনো খাটে। TypeScript শুরু হবে পর্ব ৪৭-এ; তার আগে নিয়মগুলো জানা জরুরি, কারণ TypeScript-এর error বুঝতেও এগুলো লাগে।

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

C, C++ আর Java statically typed — টাইপ থাকে variable-এর, আর ভুল টাইপ compile-এর সময়ই ধরা পড়ে (Java-র টাইপ ও ভ্যারিয়েবল)। মজার মিল: Java-তেও "5" + 3 হয় "53", কিন্তু "5" - 3 compile-ই হয় না। Python JavaScript-এর মতোই dynamically typed, কিন্তু “strongly typed”: "5" + 3 দেয় TypeError, চুপচাপ রূপান্তর করে না (Python-এর ভ্যারিয়েবল ও টাইপ)। আর Python-এ “খালি” একটাই — None; JavaScript-এ দুটি — undefined আর null.

পরীক্ষা ও ইন্টারভিউতে
  • JavaScript-এ কয়টি data type? — ৮টি: ৭টি primitive (string, number, bigint, boolean, undefined, null, symbol) আর object.
  • typeof null কী দেয়, কেন? — "object"; ১৯৯৫ সালের প্রথম বাস্তবায়নের বাগ, পুরোনো কোড ভেঙে যাওয়ার ভয়ে আর ঠিক করা হয়নি।
  • == আর ===-এর পার্থক্য? — === টাইপ আলাদা হলেই false; == আগে coercion করে তারপর তুলনা করে। === ব্যবহার করা উচিত।
  • null আর undefined-এর পার্থক্য? — undefined = মান দেওয়া হয়নি (engine বসায়); null = ইচ্ছা করে খালি (প্রোগ্রামার বসায়)। null == undefined true, null === undefined false.
  • ["1", "2", "3"].map(parseInt)-এর আউটপুট? — [1, NaN, NaN], কারণ map দ্বিতীয় argument হিসেবে index পাঠায়, যা parseInt radix ধরে নেয়।

৯ · AI-এর যুগে — টাইপের ভুল ধরার চোখ

AI কোড লেখে লাখো পুরোনো উদাহরণ থেকে শিখে — আর পুরোনো JavaScript কোডে ==, radix ছাড়া parseInt, আর “ইনপুট সবসময় ঠিক টাইপে আসবে” ধরনের অনুমান প্রচুর। ফলে AI-এর লেখা কোড প্রায়ই সাধারণ পরীক্ষায় ঠিক চলে, কিন্তু খালি ঘর, null বা অদ্ভুত ইনপুটে চুপচাপ ভুল ফল দেয় — error ছাড়াই, যা সবচেয়ে বিপজ্জনক।

AI-এর কোডে যা দেখবেনসমস্যাযা লেখা উচিত
if (status == 1)"1", true, [1] — সবই মিলে যায়status === 1
total = price + qty (form থেকে)string জোড়া লাগে: "500" + "2" → "5002"আগে Number(), তারপর যাচাই
Number(input.value)খালি ঘর হয় 0 — “দাম ০ টাকা” গ্রহণ হয়ে যায়খালি কি না আগে দেখুন, তারপর Number.isFinite
parseInt(x)radix নেই; "0x10" হয় 16parseInt(x, 10) বা Number(x)
if (!user.age)বয়স 0 হলেও “নেই” ধরেuser.age == null (বা ??, পর্ব ০৮)
user.address.cityaddress null হলে TypeErrornull/undefined সামলানো (?., পর্ব ০৮)
ফোন, আইডি Number-এশুরুর 0 আর বড় আইডির অঙ্ক হারায়string হিসেবে রাখুন

AI-কে ইনপুট-সংক্রান্ত কোড লিখতে বললে টাইপের প্রশ্নগুলো আগেই প্রম্পটে বলে দিন:

একটি JavaScript function লিখে দাও যা একটি অনলাইন দোকানের form থেকে দাম (price) আর পরিমাণ (qty) নিয়ে মোট দাম হিসাব করবে। শর্ত: ১. মনে রাখবে input.value সবসময় string — স্পষ্টভাবে Number() দিয়ে রূপান্তর করবে। ২. খালি ঘর, শুধু space, "abc", ঋণাত্মক সংখ্যা আর NaN — প্রতিটির জন্য আলাদা পরিষ্কার error বার্তা দেবে; খালি ঘরকে 0 ধরবে না। ৩. সব তুলনায় === ব্যবহার করবে; null/undefined একসাথে ধরতে শুধু == null চলবে। ৪. parseInt ব্যবহার করলে অবশ্যই radix 10 দেবে। ৫. শেষে এমন ৮টি ইনপুটের টেবিল দাও (খালি, "0", " 5 ", "5kg", null ইত্যাদি) — প্রতিটির প্রত্যাশিত ফলসহ, যাতে আমি নিজে চালিয়ে মিলাতে পারি।

AI-এর কোড হাতে পেলে — টাইপের চেকলিস্ট

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

প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন। এগুলোই আপনাকে কোড-লেখক থেকে সিস্টেম-চিন্তক বানাবে।

প্র ০১JavaScript কেন error না দিয়ে চুপচাপ টাইপ বদলে নেয়? এটা কি ভুল নকশা ছিল?

উত্তর বুঝতে ১৯৯৫ সালে ফিরতে হবে। Netscape তখন ওয়েব ব্রাউজারের বাজারে এগিয়ে, আর তারা চাইছিল পাতায় ছোট ছোট আচরণ যোগ করার একটি সহজ ভাষা — যা লিখবেন ওয়েব ডিজাইনার, শখের প্রোগ্রামার, এমনকি যাঁরা কখনো প্রোগ্রামিং শেখেননি। বড় প্রোগ্রামের জন্য ছিল Java; JavaScript-কে ভাবা হয়েছিল তার “ছোট ভাই” হিসেবে। Brendan Eich-কে মাত্র কয়েক দিনে প্রথম রূপ বানাতে হয়েছিল।

এমন ব্যবহারকারীর জন্য নকশার একটি মূল চিন্তা ছিল “ক্ষমাশীল” হওয়া। HTML নিজেও তাই — ট্যাগ বন্ধ করতে ভুললেও ব্রাউজার পাতা দেখায়। form-এর ঘর থেকে সবসময় লেখা আসে; সেই লেখা দিয়ে হিসাব করতে প্রতিবার রূপান্তর লিখতে হলে নতুনরা আটকে যেতেন। তাই ভাষা নিজেই "5" * 2-কে 10 বানিয়ে দেয়। তখনকার JavaScript-এ try/catch-ও ছিল না (তা আসে ১৯৯৯ সালের ES3-এ) — error দিয়ে পুরো script থামিয়ে দেওয়ার চেয়ে “যতটা সম্ভব চালিয়ে যাওয়া” তখন বেশি বাস্তবসম্মত মনে হয়েছিল।

Eich পরে বিভিন্ন সাক্ষাৎকার ও লেখায় স্বীকার করেছেন, বিশেষ করে ==-কে এত ঢিলেঢালা করা ছিল ভুল — শুরুর দিকের কিছু ব্যবহারকারীর অনুরোধে তিনি এটি মেনে নিয়েছিলেন। কিন্তু ওয়েবের একটি কঠোর নিয়ম আছে: “don't break the web”. লক্ষ লক্ষ পাতা পুরোনো আচরণের ওপর নির্ভর করে, তাই কোনো আচরণ একবার চালু হলে আর বদলানো যায় না। সমাধান হিসেবে ভাষা পুরোনোটা না সরিয়ে নতুন বিকল্প যোগ করেছে: ES3-এ ===, ES5-এ strict mode, ES2015-এ Number.isNaN আর Object.is, আর BigInt ও Symbol-এ ইচ্ছাকৃতভাবে কড়া নিয়ম।

তাহলে এটা কি ভুল ছিল? প্রেক্ষাপট বিবেচনায় বলা যায়, সেই সময়ের লক্ষ্যের জন্য এটি যুক্তিসঙ্গত আপস ছিল — এবং সেই সহজলভ্যতা JavaScript-কে দ্রুত ছড়িয়ে দিতে সাহায্য করেছিল। কিন্তু ভাষাটি যখন ছোট script থেকে লাখো লাইনের অ্যাপে পৌঁছাল, তখন সেই ক্ষমাশীলতাই চুপচাপ বাগের বড় উৎস হয়ে দাঁড়াল। Error দ্রুত ধরা পড়ে; ভুল ফল অনেক দূর পর্যন্ত গড়িয়ে যায়।

মূল উপলব্ধি: নকশার সিদ্ধান্ত বিচার করতে হয় তার সময় ও ব্যবহারকারীর প্রেক্ষাপটে। আর যে সিস্টেম বহু মানুষ ব্যবহার করে, তার ভুল মোছা যায় না — শুধু তার পাশে ভালো বিকল্প বানিয়ে মানুষকে সেদিকে নিতে হয়।

প্র ০২typeof null === "object" — এই বাগ কোথা থেকে এল, আর ঠিক করার প্রস্তাব কেন বাতিল হলো?

প্রথম JavaScript engine-এ প্রতিটি মান মেমরিতে রাখা হতো একটি ছোট ঘরে, যার সবচেয়ে নিচের কয়েকটি বিট ছিল type tag — মানটি কোন ধরনের তার চিহ্ন। যেমন একটি tag মানে “পূর্ণসংখ্যা”, আরেকটি “double”, “string”, “boolean”. Object-এর tag ছিল সব শূন্য (000) — বাকি বিটগুলোতে থাকত object-এর ঠিকানা।

আর null? এটি রাখা হয়েছিল C-এর NULL pointer হিসেবে — অর্থাৎ পুরো ঘরটাই শূন্য। এখন typeof-এর কোড tag দেখে: নিচের বিটগুলো শূন্য, তাই “এটা object”. null-এর জন্য আলাদা পরীক্ষা কোডে ছিল না। এভাবেই typeof null হয়ে গেল "object" — একটি সাধারণ বাস্তবায়নের অসাবধানতা, যা ভাষার আচরণ হয়ে গেল। পরে ECMAScript মানদণ্ড লেখার সময় এই আচরণই লিখে রাখা হয়, কারণ ততদিনে ওয়েবের কোড এর ওপর নির্ভর করতে শুরু করেছে।

ES2015 (তখনকার নাম “ES6” বা Harmony)-এর নকশার সময় TC39-এ একটি প্রস্তাব ওঠে: typeof null হোক "null". Brendan Eich নিজেও এর পক্ষে ছিলেন। প্রস্তাবটি নিয়ে পরীক্ষা ও আলোচনায় দেখা গেল বাস্তব ওয়েবসাইটে এমন বহু কোড আছে যা লেখে if (typeof x === "object"), তারপর ধরে নেয় null-ও এই শাখায় আসবে, বা উল্টোটা। আচরণ বদলালে সেসব পাতা কোনো সতর্কবার্তা ছাড়াই ভেঙে যেত। কমিটি প্রস্তাবটি বাতিল করে।

এর একটি ব্যবহারিক শিক্ষা আছে: typeof x === "object" লিখলে null-ও পাস করে। তাই “x কি সত্যিকারের object?” জানতে লিখতে হয় x !== null && typeof x === "object". আর array-ও “object”, তাই দরকার হলে Array.isArray. আরেকটি চতুর উপায় আছে: Object.prototype.toString.call(null) দেয় "[object Null]", আর array-র জন্য "[object Array]" — কিছু লাইব্রেরি এভাবেই টাইপ চেনে।

মূল উপলব্ধি: একটি বাস্তবায়নের খুঁটিনাটি (মেমরিতে মান কীভাবে রাখা হয়) কখনো কখনো চিরস্থায়ী ভাষা-নিয়ম হয়ে যায়। জনপ্রিয় সিস্টেমে “ভুল কিন্তু স্থির” আচরণ প্রায়ই “ঠিক কিন্তু ভাঙনধরা” পরিবর্তনের চেয়ে বেশি মূল্যবান।

প্র ০৩Gary Bernhardt-এর “Wat” বক্তৃতা কোটি মানুষকে হাসিয়েছে। Coercion কি সত্যিই “পাগলামি”, নাকি শুধু অচেনা নিয়ম?

২০১২ সালে CodeMash সম্মেলনে Gary Bernhardt কয়েক মিনিটের একটি lightning talk দেন, নাম “Wat”. প্রথমে Ruby, তারপর JavaScript-এর কিছু অদ্ভুত ফল দেখান: [] + [] খালি string, [] + {} হলো "[object Object]", অথচ {} + [] হলো 0, আর {} + {} হলো NaN. প্রতিটিতে দর্শকেরা হো হো করে হেসেছেন — “wat?” ভিডিওটি প্রোগ্রামারদের মধ্যে কিংবদন্তি হয়ে গেছে, আর আজও “JavaScript অদ্ভুত” প্রমাণে এটি দেখানো হয়।

কিন্তু এই পর্বের নিয়ম দিয়ে প্রতিটি ব্যাখ্যা করা যায়। [] + []: ToPrimitive → "" + "". {} + [] → 0: লাইনের শুরুতে {} একটি খালি block, বাকি থাকে +[] = 0. {} + {} → NaN: আবার block, তারপর +{} = ToNumber("[object Object]") = NaN. মজার বিষয়, আধুনিক Chrome বা Node.js-এর console-এ এগুলো লিখলে ভিন্ন ফল পাবেন, কারণ console এখন { দিয়ে শুরু হওয়া ইনপুটকে expression হিসেবে পড়ে।

তাহলে এটি “পাগলামি” নয়, নির্দিষ্ট ও লিখিত নিয়ম। তবু সমালোচনা পুরোপুরি অন্যায্য নয়। একটি ভাষার নিয়ম ভালো কি না, তা শুধু “নিয়ম আছে কি না” দিয়ে মাপা যায় না — মাপতে হয় নিয়মগুলো মানুষের প্রত্যাশার কতটা কাছাকাছি। ==-এর transitive না হওয়া ("" == 0, 0 == "0", কিন্তু "" != "0") বা null == 0 false অথচ null >= 0 true — এগুলো নিয়ম মেনেও বিস্ময়কর, আর বিস্ময় মানেই বাগের সুযোগ। ভাষা-নকশায় একে বলে “principle of least astonishment”.

আরেকটি দিক: Wat-এর বেশিরভাগ উদাহরণ বাস্তব কোডে কেউ লেখে না — কে-ই বা array-র সাথে object যোগ করে? আসল ক্ষতি করে সাধারণ ক্ষেত্রগুলো: form-এর string যোগ, খালি ঘর 0 হয়ে যাওয়া, ==-এ ভুল মিল। তাই ধাঁধা মুখস্থ করার চেয়ে মূল চারটি নিয়ম আর কয়েকটি অভ্যাস (===, স্পষ্ট রূপান্তর) অনেক বেশি কাজের।

মূল উপলব্ধি: “অদ্ভুত” আর “নিয়মহীন” এক জিনিস নয়। কোনো আচরণ দেখে হাসার আগে নিয়মটা খুঁজুন — তারপর বিচার করুন নিয়মটা ভালো কি না। প্রকৌশলীর কাজ দুটিই: নিয়ম বোঝা, এবং খারাপ নিয়মের ঝুঁকি থেকে নিজের কোড রক্ষা করা।

প্র ০৪=== থাকতেও ES2015-এ Object.is কেন যোগ হলো? NaN আর -0 নিয়ে এত মাথাব্যথা কেন?

===-এর দুটি “ব্যতিক্রম” আসে সরাসরি IEEE 754 floating-point মানদণ্ড (প্রথম প্রকাশ ১৯৮৫) থেকে, যা প্রায় সব আধুনিক CPU আর ভাষা মেনে চলে। প্রথমটি: NaN কোনো কিছুর সমান নয়, নিজেরও নয়। দ্বিতীয়টি: শূন্য দুই রকম — +0 আর -0 — কিন্তু তুলনায় এরা সমান। দুটোরই গাণিতিক যুক্তি আছে। ব্যর্থ হিসাবের দুটি ফলকে “সমান” বললে ভুল সিদ্ধান্ত হতে পারে। আর -0 বোঝায় “খুব ছোট ঋণাত্মক সংখ্যা যা শূন্যে গোল হয়েছে” — দিকের তথ্য রাখে: 1 / -0 হলো -Infinity, 1 / 0 হলো Infinity.

সমস্যা হয় যখন প্রশ্নটা গাণিতিক তুলনা নয়, বরং “এই দুটি কি হুবহু একই মান?” যেমন: একটি cache-এ দেখা, আগে এই মান দেখা হয়েছে কি না; বা একটি state বদলেছে কি না। === দিয়ে NaN কখনো খুঁজে পাওয়া যায় না — [NaN].indexOf(NaN) দেয় -1. আবার 0 আর -0-কে একই ধরলে দিকের তথ্য হারায়।

ES2015 এর জন্য মানদণ্ডের ভেতরের “SameValue” তুলনাকে Object.is নামে প্রকাশ করে: NaN নিজের সমান, আর +0 ও -0 আলাদা। একই সময়ে আরেকটি রূপ আছে — “SameValueZero”: NaN নিজের সমান, কিন্তু +0 আর -0 সমান। Map, Set আর ES2016-এর Array.prototype.includes এটি ব্যবহার করে। তাই [NaN].includes(NaN) হলো true, যদিও indexOf তা খুঁজে পায় না।

তাহলে JavaScript-এ আসলে চারটি সমতা: == (coercion সহ), === (IEEE নিয়ম), SameValueZero (includes, Map, Set) আর SameValue (Object.is). বাস্তবে React useState-এর নতুন মান আগেরটির সমান কি না দেখতে Object.is ব্যবহার করে — যাতে NaN থেকে NaN-এ “বদল” অপ্রয়োজনীয় নতুন render না ঘটায়।

মূল উপলব্ধি: “সমান” একটি প্রশ্ন নয়, কয়েকটি ভিন্ন প্রশ্ন — গাণিতিকভাবে সমান, হুবহু একই মান, বা একই object. ভালো API প্রতিটি প্রশ্নের জন্য স্পষ্ট টুল দেয়; প্রোগ্রামারের দায়িত্ব হলো জানা, কোন প্রশ্নটা তিনি আসলে করছেন।

প্র ০৫Dynamic না static typing — কোনটি ভালো? আর JavaScript-এর জগতে TypeScript এত জনপ্রিয় হলো কেন?

এই বিতর্ক প্রোগ্রামিংয়ের সবচেয়ে পুরোনোগুলোর একটি। Static typing-এর (C, Java, Rust, Go) পক্ষে যুক্তি: অনেক ভুল চালানোর আগেই ধরা পড়ে; editor নিশ্চিতভাবে autocomplete দিতে পারে; বড় কোডে নাম বদলানো (refactoring) নিরাপদ; টাইপ নিজেই একধরনের ডকুমেন্টেশন। Dynamic typing-এর (JavaScript, Python, Ruby) পক্ষে যুক্তি: দ্রুত শুরু করা যায়, কম লিখতে হয়, ছোট script আর পরীক্ষামূলক কাজে নমনীয়তা বেশি, নতুনদের জন্য প্রবেশ সহজ।

অভিজ্ঞতা দেখিয়েছে, প্রজেক্ট যত বড় হয় আর যত বেশি মানুষ একসাথে কাজ করে, static typing-এর সুবিধা তত বাড়ে। ২০১০-এর দশকে JavaScript দিয়ে বিশাল অ্যাপ লেখা শুরু হলে এই চাপ তীব্র হয়। Google আগেই Closure Compiler-এ comment-এর ভেতরে টাইপ লেখার ব্যবস্থা করেছিল। ২০১২ সালে Microsoft প্রকাশ করে TypeScript — নকশার নেতৃত্বে ছিলেন Anders Hejlsberg, যিনি আগে Turbo Pascal আর C# তৈরি করেছিলেন। ২০১৪ সালে Facebook আনে Flow. শেষ পর্যন্ত TypeScript-ই ব্যাপকভাবে জনপ্রিয় হয়।

TypeScript-এর সাফল্যের কারণ ছিল কয়েকটি চতুর সিদ্ধান্ত। এটি JavaScript-এর superset — যেকোনো বৈধ JavaScript প্রায় সরাসরি TypeScript, তাই ধীরে ধীরে, ফাইল ধরে ধরে গ্রহণ করা যায়। টাইপ শুধু compile-এর সময় থাকে, চালানোর সময় মুছে যায় — তাই runtime আচরণ হুবহু JavaScript, আর এই পর্বের সব coercion তখনো খাটে। আর এর টাইপ-ব্যবস্থা JavaScript-এর বাস্তব ধরন মেনে বানানো: string | null-এর মতো union, আর কোডের if দেখে টাইপ সংকুচিত করা (narrowing). এমনকি সাধারণ JavaScript লেখার সময়ও VS Code-এর autocomplete পেছনে TypeScript-এর engine ব্যবহার করে।

বিতর্কটা এখন অনেকটা মিলনের দিকে। Python ২০১৫ সালে ঐচ্ছিক type hint যোগ করেছে। TC39-এ JavaScript-এ সরাসরি টাইপ লেখার (চালানোর সময় উপেক্ষা করা হবে এমন) একটি প্রস্তাব আলোচনায় আছে। আর Node.js-এর সাম্প্রতিক version-গুলো TypeScript ফাইল থেকে টাইপ মুছে সরাসরি চালাতে পারে। অর্থাৎ শিল্প বেছে নিচ্ছে “gradual typing” — যেখানে দরকার টাইপ, যেখানে দরকার নেই সেখানে নমনীয়তা।

মূল উপলব্ধি: কোনো typing ব্যবস্থা সব জায়গায় সেরা নয় — এটি দ্রুততা আর নিশ্চয়তার মধ্যে আপস। ছোট script-এ dynamic typing আরামদায়ক; বহুজনের বড় প্রজেক্টে টাইপ-যাচাই বিনিয়োগ ফেরত দেয়। আর যেকোনো পথেই runtime-এর নিয়ম জানা অপরিহার্য।

অনুশীলন

প্রথমে খাতায় উত্তর লিখুন, তারপর ব্রাউজারের console-এ চালিয়ে মেলান।

  1. আগে অনুমান, পরে যাচাই: প্রতিটির ফল ও টাইপ লিখুন — (ক) "3" * "4" (খ) "3" + 4 + 5 (গ) 3 + 4 + "5" (ঘ) null + 1 (ঙ) undefined + 1

    (ক) 12 (number) — * শুধু সংখ্যা বোঝে, দুটোই ToNumber. (খ) "345" — বাম থেকে: "3" + 4 = "34", তারপর "34" + 5 = "345". (গ) "75" — আগে 3 + 4 = 7 (দুটোই number), তারপর 7 + "5" = "75". (ঘ) 1 — কোনো string নেই, null → 0. (ঙ) NaN — undefined → NaN, আর NaN-এর সাথে যেকোনো হিসাব NaN. (ঘ) আর (ঙ)-এর পার্থক্যই দেখায় null আর undefined রূপান্তরেও আলাদা।

  2. API-র তিন অবস্থা: { phone: "017…" }, { phone: null } আর {} — এই তিনটি object আলাদা করে চেনার শর্ত লিখুন। কোন শর্ত null আর অনুপস্থিত ঘর দুটোকেই একসাথে ধরে?

    নম্বর আছে: typeof u.phone === "string". ইচ্ছা করে খালি: u.phone === null. ঘরটাই নেই: !("phone" in u) (বা Object.hasOwn(u, "phone") false). শুধু u.phone === undefined দেখলে “ঘর নেই” আর “ঘর আছে কিন্তু মান undefined” আলাদা করা যায় না — in লাগে। দুটোকে একসাথে ধরতে: u.phone == null — null আর undefined দুটোতেই true, অন্য কিছুতে নয়। বেশিরভাগ UI-তে (“নম্বর দেওয়া নেই” দেখানো) এটুকুই যথেষ্ট।

  3. map আর parseInt-এর ধাঁধা: ["1", "2", "3"].map(parseInt) দেয় [1, NaN, NaN]. কেন? (ইঙ্গিত: map callback-কে কয়টি argument দেয়?)

    map প্রতিটি উপাদানের জন্য callback-কে তিনটি argument দেয়: মান, index, পুরো array. parseInt দ্বিতীয় argument-কে radix ধরে। তাই আসলে চলে parseInt("1", 0) → radix 0 মানে “নিজে ঠিক করো” → 1; parseInt("2", 1) → radix 1 অবৈধ → NaN; parseInt("3", 2) → binary-তে “3” অঙ্ক নেই → NaN. সমাধান: .map(Number) বা .map(s => parseInt(s, 10)). শিক্ষা: কোনো function-কে সরাসরি callback হিসেবে দেওয়ার আগে দেখুন সে কয়টি argument নেয় (পর্ব ১৬)।

  4. নিরাপদ রূপান্তর লিখুন: একটি function toAmount(text) লিখুন, যা form-এর লেখা নিয়ে সত্যিকারের সংখ্যা ফেরত দেবে, আর খালি, শুধু space বা সংখ্যা নয় এমন লেখায় null দেবে। "0"-এ অবশ্যই 0 দিতে হবে।

    একটি সমাধান:

    function toAmount(text) {
      if (typeof text !== "string") return null;
      const t = text.trim();
      if (t === "") return null;          // খালি ঘর 0 নয়!
      const n = Number(t);
      return Number.isFinite(n) ? n : null;
    }
    // toAmount("0") → 0, toAmount("  ") → null,
    // toAmount("12.5") → 12.5, toAmount("5kg") → null

    মূল বিষয় তিনটি: খালি লেখা আগে আলাদা করে ধরা (নইলে Number("") দেয় 0); parseFloat নয়, কড়া Number ব্যবহার (নইলে "5kg" হয়ে যায় 5); আর Number.isFinite দিয়ে NaN ও Infinity বাদ দেওয়া। ফেরত দেওয়ার পর if (!amount) নয়, if (amount === null) দিয়ে যাচাই করবেন — নইলে বৈধ 0-কেও বাতিল করবেন।

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

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

ক্রম[] == false হিসাব করার ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. টাইপ আলাদা: এক পাশে object, অন্য পাশে boolean
  2. boolean-কে number বানানো: false → 0
  3. object-কে ToPrimitive: [] → ""
  4. string বনাম number: "" → 0
  5. 0 == 0 — ফল true

JavaScript-এ typeof null কী ফেরত দেয়?

  • "null"
  • "undefined"
  • "object"
  • TypeError দেয়

একটি form-এর দুটি input-এর value হলো "500" আর "50". a.value + b.value কী দেবে?

  • 550
  • "50050"
  • NaN
  • TypeError

নিচের কোনটির ফল true?

  • NaN === NaN
  • null == 0
  • null === undefined
  • 0 == ""

parseInt("01712345678", 10)-এর ফল কী, আর ফোন নম্বর রাখার জন্য কোনটি ঠিক?

  • 1712345678 — শুরুর 0 হারায়, তাই ফোন নম্বর string হিসেবে রাখা উচিত
  • 01712345678 — number-এ রাখলেই চলে
  • একটি octal সংখ্যা — তাই number-এ রাখা ভালো
  • NaN — তাই ফোন নম্বর পড়া যায় না

একটি মান null অথবা undefined কি না — দুটোই একসাথে, কিন্তু 0 বা খালি string বাদে — যাচাইয়ের সবচেয়ে ছোট নির্ভরযোগ্য উপায় কোনটি?

  • !x
  • typeof x === "undefined"
  • x == null
  • x === null

isNaN("abc") আর Number.isNaN("abc")-এর ফল কী?

  • true আর true
  • true আর false
  • false আর true
  • false আর false

শব্দকোষ

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

মনে রাখুন

  1. টাইপ থাকে মানের, variable-এর নয় — JavaScript dynamically typed. টাইপ ৮টি: ৭টি primitive (অপরিবর্তনীয়, মান কপি হয়) আর object (reference ভাগ হয়)।
  2. typeof-এর ফাঁকি: null → "object", array → "object", function → "function", NaN → "number". নিশ্চিত যাচাই: x === null, Array.isArray, Number.isNaN, Number.isFinite.
  3. undefined = মান দেওয়া হয়নি; null = ইচ্ছা করে খালি। দুটো একসাথে ধরতে x == null.
  4. +-এ এক পাশে string থাকলে string জেতে; - * / % সংখ্যা বানায়। form-এর মান সবসময় string — হিসাবের আগে Number(), আর parseInt-এ radix 10. ফোন নম্বর ও আইডি string.
  5. সবসময় ===; NaN নিজের সমান নয়; Object.is NaN আর -0 ঠিকভাবে চেনে। ধাঁধা দেখলে মুখস্থ নয় — নিয়ম খাটান।

পরের পর্বে: মান রাখার পাত্র — variable. let, const আর পুরোনো var-এর পার্থক্য, scope কী, hoisting কেন ঘটে, আর TDZ নামের সেই রহস্যময় অঞ্চল, যেখানে variable আছে কিন্তু ছোঁয়া যায় না।

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

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