এই পর্বে যা শিখবেন
- গাণিতিক, assignment, তুলনা আর logical অপারেটর — প্রতিটির আসল আচরণ, ফাঁদসহ
- Truthy আর falsy কী, আর কোন ৮টি মান falsy
&&আর||কেন operand ফেরত দেয়, short-circuit কীভাবে কাজ করে??,?.আর&&=,||=,??=দিয়ে অনুপস্থিত ডেটা নিরাপদে সামলানো- Ternary,
typeof,in, bitwise অপারেটর আর অগ্রাধিকার (precedence) ও associativity-র নিয়ম
১ · অপারেটর কী — মান মিলিয়ে নতুন মান
চায়ের দোকানে হিসাব করার সময় দোকানদার বলেন, “দুই কাপ চা ১৫ টাকা করে, সাথে একটা বিস্কুট ১০ টাকা।” মাথায় হিসাব চলে 2 * 15 + 10. এখানে তিনটি সংখ্যা আছে, আর দুটি চিহ্ন — * আর +. চিহ্নগুলো সংখ্যাগুলোকে মিলিয়ে একটি নতুন মান বানায়: ৪০।
প্রোগ্রামিংয়ে এই চিহ্নগুলোকে বলে operator, আর যাদের ওপর কাজ করে তাদের বলে operand. Operator আর operand মিলে তৈরি হয় একটি expression — এমন কোড, যা হিসাব করলে একটি মান পাওয়া যায়। পুরো পর্বের মূল কথা একটি বাক্যে: প্রতিটি operator কিছু মান নেয় আর একটি নতুন মান ফেরত দেয়। কোন মান ফেরত দেয়, সেটিই এই পর্বে খুঁটিয়ে দেখব।
Operand কয়টি লাগে, সেই হিসাবে operator তিন রকম:
| ধরন | Operand | উদাহরণ |
|---|---|---|
| Unary | ১টি | -x, !ok, typeof v, i++ |
| Binary | ২টি | a + b, a === b, a && b |
| Ternary | ৩টি | age >= 18 ? "ভোটার" : "শিশু" — JavaScript-এ এই একটিই |
বেশিরভাগ ভাষায় logical অপারেটর (&&, ||) সবসময় true বা false দেয়। JavaScript এখানে আলাদা: এরা ফেরত দেয় নিজেদের একটি operand, আর কোনটি দেবে তা ঠিক হয় সেই মান “truthy” না “falsy” তা দেখে। এই একটি নিয়ম থেকেই এসেছে JavaScript-এর অনেক জনপ্রিয় কৌশল — আর অনেক বাগও। তাই আধুনিক JavaScript-এ দুটি নতুন অপারেটর যোগ হয়েছে, ?? আর ?. — অনুপস্থিত ডেটা নিরাপদে সামলানোর জন্য।
আগের পর্বের সাথে যোগ: পর্ব ০৬-এ দেখেছেন, ভিন্ন টাইপের মান মেলালে JavaScript নিজে থেকেই টাইপ বদলায় (coercion), আর ==-এর বদলে === কেন নিরাপদ। এই পর্বে সেগুলো শুধু মনে করিয়ে দেব; মূল মনোযোগ অপারেটরগুলোর আচরণে।
২ · গাণিতিক অপারেটর — যোগ-বিয়োগ থেকে ভাগশেষ আর ঘাত
প্রথমে চেনা জিনিস। JavaScript-এ সংখ্যার হিসাবের জন্য সাতটি মূল operator:
| Operator | কাজ | উদাহরণ → ফল |
|---|---|---|
+ | যোগ (string হলে জোড়া লাগানো) | 7 + 2 → 9; "3" + 1 → "31" |
- | বিয়োগ | 7 - 2 → 5; "3" - 1 → 2 |
* | গুণ | 7 * 2 → 14 |
/ | ভাগ — সবসময় দশমিকসহ | 7 / 2 → 3.5 |
% | ভাগশেষ (remainder) | 7 % 2 → 1 |
** | ঘাত (exponentiation), ES2016 থেকে | 2 ** 10 → 1024 |
++ / -- | এক বাড়ানো / এক কমানো (increment) | i++, --i |
খেয়াল করুন, 7 / 2 দেয় 3.5, C বা Java-র মতো 3 নয়। JavaScript-এ সাধারণ সংখ্যার একটিই টাইপ — number, যা ভেতরে ৬৪-বিট floating-point (দশমিক বিন্দুওয়ালা) সংখ্যা। পূর্ণসংখ্যার ভাগ চাইলে লিখতে হয় Math.trunc(7 / 2). দশমিক সংখ্যার কারণে 0.1 * 3 কেন 0.30000000000000004 দেয়, সেই গল্প পর্ব ১০-এ।
+ দুটি কাজ করে — যোগ আর string জোড়া। কোনো একটি operand string হলেই জোড়ার কাজ জেতে, তাই "3" + 1 হয় "31". কিন্তু -, *, / শুধু সংখ্যা চেনে, তাই string-কে সংখ্যায় বদলে নেয়। পর্ব ০৬-এর এই ফাঁদটি ফর্মের ইনপুটে সবচেয়ে বেশি ধরা পড়ে — ইনপুট বক্সের মান সবসময় string.
ভাগশেষ আর ঋণাত্মক সংখ্যা
% দিয়ে আমরা জোড়-বিজোড় যাচাই করি (n % 2 === 0), বা ঘড়ির হিসাব করি। কিন্তু ঋণাত্মক সংখ্যায় একটি চমক আছে: JavaScript-এ %-এর ফলের চিহ্ন হয় ভাজ্যের (বাম দিকের সংখ্যার) মতো।
-7 % 3 // -1 (গণিতের "mod" হলে 2 হতো)
7 % -3 // 1
((-7 % 3) + 3) % 3 // 2 — সবসময় ধনাত্মক ভাগশেষ চাইলে
5.5 % 2 // 1.5 — দশমিকেও চলেএজন্য JavaScript-এর %-কে “modulo” না বলে “remainder” বলা ঠিক। সপ্তাহের দিন হিসাব করার মতো কাজে, যেখানে ঋণাত্মক সংখ্যা আসতে পারে (যেমন “আজ থেকে ১০ দিন আগে কোন বার”), শেষ লাইনের মতো করে লিখুন — নইলে -1 নম্বর দিন পেয়ে array থেকে undefined আসবে। Python-এ -7 % 3 দেয় 2; এখানে ভাষাগুলো আলাদা।
ঘাত — **, আর দুটি বিশেষ নিয়ম
ES2016-এর আগে ঘাতের জন্য লিখতে হতো Math.pow(2, 10); এখন 2 ** 10. দুটি নিয়ম মনে রাখুন। প্রথমত, ** ডান দিক থেকে হিসাব হয়: 2 ** 3 ** 2 মানে 2 ** (3 ** 2) = 2 ** 9 = 512, 64 নয় — গণিতে 232 যেভাবে পড়ি। দ্বিতীয়ত, -2 ** 2 লিখলে ফল নয়, error আসে:
SyntaxError: Unary operator used immediately before exponentiation expression. Parenthesis must be used to disambiguate operator precedence
এটি V8-এর (Chrome, Edge, Node.js) বার্তা। গণিতে −2² মানে −4, কিন্তু অনেকে পড়েন (−2)² = 4. এই দ্বিধা এড়াতে JavaScript আপনাকে বন্ধনী দিয়ে স্পষ্ট করতে বাধ্য করে: (-2) ** 2 → 4, বা -(2 ** 2) → -4. পঞ্চম ভাবনার প্রশ্নে এর ইতিহাস আছে।
++ আর -- — আগে না পরে
i++ (postfix) আগে পুরোনো মান ফেরত দেয়, তারপর বাড়ায়। ++i (prefix) আগে বাড়ায়, তারপর নতুন মান দেয়। let n = 5; হলে n++ দেয় 5 (এখন n = 6), তারপর ++n দেয় 7. আলাদা লাইনে একা লিখলে (i++;) দুটির কোনো পার্থক্য নেই। সমস্যা হয় যখন একই expression-এ মিশিয়ে লেখা হয় — arr[i++] = i জাতীয় কোড পড়তে কঠিন, এড়িয়ে চলুন। অনেক style guide তাই i += 1 লেখার পরামর্শ দেয়।
৩ · Assignment আর তুলনা — মান বসানো, মান মেলানো
= গণিতের “সমান” নয় — এটি “ডান দিকের মান বাম দিকের variable-এ বসাও”। আর এটিও একটি expression: x = 5 নিজে 5 ফেরত দেয়। এজন্যই a = b = 0 কাজ করে — ডান দিক থেকে: আগে b = 0, তার ফল 0 যায় a-তে। আবার এজন্যই if (x = 5) লিখলে (===-এর বদলে ভুল করে) কোনো error আসে না — মান বসে, আর শর্ত সবসময় সত্য হয়।
কোনো variable-এর নিজের মানের সাথে হিসাব করে আবার তাতেই রাখার জন্য আছে compound assignment:
| লেখা | মানে | যেমন (total = 100) |
|---|---|---|
total += 50 | total = total + 50 | 150 |
total -= 20 | total = total - 20 | 80 |
total *= 2 | total = total * 2 | 200 |
total /= 4, %=, **= | একই ধাঁচ | 25, … |
&=, |=, ^=, <<= … | bitwise-এর জন্য (অংশ ৮) | — |
&&=, ||=, ??= | logical assignment, ES2021 (অংশ ৬) | — |
তুলনা — < > <= >=
তুলনার অপারেটর সবসময় true বা false দেয়। সংখ্যায় এরা যা আশা করেন তাই করে। আসল মজা string-এ। দুটি string তুলনা হয় অক্ষর ধরে ধরে, প্রতিটি অক্ষরের Unicode নম্বর দেখে — অভিধানের মতো, কিন্তু অভিধানের নিয়মে নয়। আরও নির্দিষ্টভাবে, JavaScript string-কে UTF-16 code unit-এর সারি হিসেবে রাখে, আর তুলনা হয় সেই নম্বরগুলো দিয়ে (বিস্তারিত পর্ব ০৯-এ)।
| তুলনা | ফল | কেন |
|---|---|---|
"apple" < "banana" | true | প্রথম অক্ষর a (97) < b (98) |
"Zebra" < "apple" | true | সব বড় হাতের অক্ষর (65–90) ছোট হাতের (97–122) আগে |
"10" < "9" | true | দুটিই string — প্রথম অক্ষর "1" < "9", বাকিটা দেখাই হয় না |
"10" < 9 | false | একটি number, তাই "10" সংখ্যা 10-এ বদলায় |
"৯" < "১০" | false | বাংলা অঙ্কও অক্ষর: ৯ (U+09EF) > ১ (U+09E7) |
শিক্ষার্থীর রোল নম্বর বা দাম string হিসেবে এলে (যেমন JSON বা ফর্ম থেকে) সাজানো ভুল হয় — "100" আসে "20"-এর আগে। তাই তুলনার আগে Number() দিয়ে সংখ্যায় বদলে নিন। আর মানুষের পড়ার মতো করে নাম সাজাতে (বড়-ছোট হাতের অক্ষর বা বাংলা বর্ণমালার নিয়মে) localeCompare ব্যবহার করুন — পর্ব ০৯ আর ২০-এ দেখবেন।
ভিন্ন টাইপের মান তুলনা করলে JavaScript সাধারণত দুটিকেই সংখ্যায় বদলায়: null >= 0 হয় true (null → 0), কিন্তু undefined > -1 হয় false (undefined → NaN, আর NaN-এর সাথে প্রতিটি তুলনা false). আরেকটি ক্লাসিক: 1 < 2 < 3 → true, কিন্তু 3 > 2 > 1 → false. কারণ বাম থেকে হিসাব: 3 > 2 = true, তারপর true > 1 মানে 1 > 1 = false. শিকল করা তুলনা JavaScript-এ গণিতের মতো কাজ করে না; লিখুন a > b && b > c.
সমতা যাচাইয়ে সবসময় strict equality === আর !== ব্যবহার করুন — এরা টাইপ বদলায় না। == টাইপ বদলে মেলায়, তাই 0 == "" আর [] == false দুটিই true (পর্ব ০৬)। দুটি বিশেষ ক্ষেত্র: NaN === NaN হলো false (NaN যাচাইয়ে Number.isNaN(x)), আর 0 === -0 হলো true. দুটিকেই আলাদা করে চিনতে Object.is().
৪ · Truthy আর falsy — “হ্যাঁ” আর “না”-এর বাইরের মান
if-এর শর্তে আপনি শুধু true/false নয়, যেকোনো মান দিতে পারেন: if (name), if (cart.length), if (user). তখন JavaScript মানটিকে মনে মনে boolean-এ বদলে নেয়। যে মান false-এ বদলায় তাকে বলে falsy, আর যে মান true-এ বদলায় তাকে truthy.
সুখবর হলো, falsy মানের তালিকা ছোট আর নির্দিষ্ট — ঠিক ৮টি। এগুলো মুখস্থ রাখুন; বাকি সব truthy.
❌ ৮টি falsy মান
false · 0 · -0 · 0n (BigInt শূন্য) · "" (ফাঁকা string) · null · undefined · NaN
document.all)
✔️ চমক দেওয়া truthy মান
"0" · "false" · " " (একটি space) · [] (ফাঁকা array) · {} (ফাঁকা object) · -1 · Infinity · যেকোনো function
মেট্রো রেলের গেটের কথা ভাবুন। গেট দেখে না কার্ডে কত টাকা, কার নাম — শুধু দেখে “কার্ড বৈধ, না অবৈধ”। অবৈধ কার্ডের তালিকা ছোট আর নির্দিষ্ট; বাকি সবাই ঢুকতে পারে। if ঠিক এমন গেট। "0" দেখতে শূন্যের মতো হলেও এটি একটি অক্ষরওয়ালা string — গেট খুলে যায়।
যেকোনো মানের truthiness নিজে দেখতে Boolean(v) বা দুবার “না” — !!v লিখুন: !!"0" → true, !!NaN → false. একক ! মানটিকে boolean-এ বদলে উল্টে দেয়; দুবার দিলে শুধু বদলটুকু থাকে।
ফাঁকা array আর object truthy. if (items) লিখে “তালিকা ফাঁকা কি না” যাচাই হয় না — [] truthy, তাই শর্ত সবসময় সত্য। লিখুন if (items.length > 0). object-এর ক্ষেত্রে Object.keys(obj).length > 0. আবার উল্টো ফাঁদ: if (count) লিখলে count = 0 হলে শর্ত মিথ্যা — “শূন্যটি আইটেম” আর “count নেই” আলাদা করা যায় না। এই ফাঁদই অংশ ৬-এর মূল গল্প।
৫ · &&, || আর ! — operand ফেরত, আর short-circuit
এবার পর্বের সবচেয়ে গুরুত্বপূর্ণ নিয়ম। JavaScript-এর logical অপারেটর এভাবে কাজ করে:
a || b — “অথবা”
a truthy হলে a-ই ফেরত দেয়, b-কে ছোঁয়ও না। a falsy হলে b ফেরত দেয় (যা-ই হোক)।
a && b — “এবং”
a falsy হলে a-ই ফেরত দেয়, b-কে ছোঁয়ও না। a truthy হলে b ফেরত দেয়।
তাই "রহিম" || "অতিথি" দেয় "রহিম", true নয়। আর 0 && "কিছু" দেয় 0, false নয়। if-এর ভেতরে এই পার্থক্য চোখে পড়ে না, কারণ if ফলটিকে আবার truthy/falsy হিসেবে দেখে। কিন্তু ফলটি কোনো variable-এ রাখলে বা পাতায় দেখালে পার্থক্য স্পষ্ট। শুধু ! সবসময় সত্যিকারের boolean দেয়।
দ্বিতীয় অংশটিও সমান গুরুত্বপূর্ণ: short-circuit evaluation. বাম দিক দেখেই উত্তর জানা হয়ে গেলে ডান দিকের expression চালানোই হয় না। “অথবা”-তে একটি সত্য পেলেই যথেষ্ট; “এবং”-এ একটি মিথ্যা পেলেই যথেষ্ট। এর সবচেয়ে কাজের ব্যবহার — নেই এমন object-এর property পড়ার আগে পাহারা বসানো:
1let user = null;2const name = user && user.name;3console.log(name);4user = { name: "সুমি" };5console.log(user && user.name);
| user | |
| বাম দিক | |
| ডান দিক চলল? | |
| name |
- শুরু: user নেই। ধরুন সার্ভার থেকে লগইন করা ব্যবহারকারী আসেনি, তাই
userহলোnull. সরাসরিuser.nameপড়লে আসতTypeError: Cannot read properties of null (reading 'name'). - বাম দিক আগে। লাইন ২-এ
&&প্রথমে বাম দিক হিসাব করে:user→null. এটি ৮টি falsy মানের একটি। - থেমে যাওয়া — short-circuit. “এবং”-এর একটি দিক মিথ্যা হলে পুরোটা মিথ্যা — ডান দিক দেখার দরকার নেই। তাই
user.nameচালানোই হলো না, কোনো error নেই।&&ফেরত দিল বাম দিকের মানnull, সেটিই গেলname-এ। - ছাপা হলো null. খেয়াল করুন, ফল
falseনয় —null.&&নিজের operand-ই ফেরত দেয়। - এবার user আছে। লাইন ৪-এ object বসল। লাইন ৫-এ বাম দিক truthy, তাই
&&ডান দিক চালাল আর তার মান"সুমি"ফেরত দিল। (একই কাজ আধুনিক JavaScript-এ আরও পরিষ্কারভাবে করেuser?.name— অংশ ৭।)
Short-circuit-এর আরও কয়েকটি পরিচিত ব্যবহার: isLoggedIn && showDashboard() — লগইন থাকলেই function চলবে; callback && callback() — callback দেওয়া থাকলেই ডাকা হবে। এগুলো চলে, তবে if দিয়ে লিখলে সাধারণত পড়তে সহজ হয়। React-এর মতো framework-এ {count && <List />} লেখার একটি বিখ্যাত বাগ আছে: count = 0 হলে পাতায় একটি “0” দেখায় — কারণ && falsy বাম দিক 0-কেই ফেরত দেয়।
বাস্তব উদাহরণ — ছাড় পাবেন কে?
একটি অনলাইন দোকানের নিয়ম: “সদস্য হলে এবং কেনাকাটা ৫০০ টাকা বা বেশি হলে ছাড়; অথবা কুপন থাকলে যেকোনো অবস্থায় ছাড়।”
const isMember = true;
const total = 450;
const hasCoupon = false;
const eligible = (isMember && total >= 500) || hasCoupon;
// (true && false) || false → false || false → falseএখানে প্রতিটি operand নিজেই boolean, তাই ফলও boolean — false. &&-এর অগ্রাধিকার ||-এর চেয়ে বেশি, তাই বন্ধনী না দিলেও একই ফল আসত; তবু বন্ধনী দিন — পাঠক (আর ছয় মাস পরের আপনি) এক নজরে নিয়মটি বুঝবেন। যদি operand-গুলো boolean না হয়ে অন্য মান হতো (যেমন hasCoupon-এ কুপন কোড string), তাহলে eligible-এ boolean না এসে সেই string আসত। নিশ্চিত boolean চাইলে লিখুন Boolean(…).
৬ · ডিফল্ট মান — ||-এর বাগ আর ??-এর সমাধান
বহু বছর ধরে JavaScript-এ ডিফল্ট মান দেওয়ার প্রচলিত কৌশল ছিল ||: const name = input || "অতিথি"; — input ফাঁকা হলে “অতিথি”। কাজ করে, যতক্ষণ না বৈধ মানটি নিজেই falsy হয়।
একটি ই-কমার্স সাইট ভাবুন। দোকানদার প্রতিটি পণ্যের ডেলিভারি চার্জ ঠিক করতে পারেন; না দিলে ডিফল্ট ৬০ টাকা। একটি পণ্যে তিনি ইচ্ছে করে লিখলেন 0 — ফ্রি ডেলিভারি। কোডে আছে const charge = product.deliveryCharge || 60;. ফল: ক্রেতার কাছ থেকে ৬০ টাকা কাটা হলো! কারণ 0 falsy, আর ||-এর চোখে falsy মানে “নেই”।
এই সমস্যার জন্য ES2020-এ এসেছে nullish coalescing অপারেটর ??. a ?? b মানে: a যদি null বা undefined হয় তবেই b, নইলে a. এই দুটি মানকে একসাথে বলে nullish — “সত্যিই কোনো মান নেই”। 0, "", false — এরা মান, তাই ?? এদের রেখে দেয়।
| qty | |
| qty || 1 | |
| qty ?? 1 |
- ইনপুট এল: qty = 0. ব্যবহারকারী কার্টে একটি আইটেমের পরিমাণ ০ করেছেন — তিনি সেটি সরাতে চান। কোডে ডিফল্ট ১ দেওয়া আছে, যাতে কিছু না লিখলে অন্তত একটি আইটেম ধরা হয়।
||প্রশ্ন করে: “truthy?”||দেখে0truthy কি না।0৮টি falsy মানের একটি, তাই||ধরে নেয় মান “নেই”, আর ডান দিকে যায়।- বাগ: ফল 1. ব্যবহারকারী আইটেম সরাতে চাইলেন, কিন্তু কার্টে রয়ে গেল একটি। কোনো error নেই, কোনো সতর্কবার্তা নেই — চুপচাপ ভুল। এমন বাগ টেস্টে ধরা পড়ে না, যদি কেউ ০ দিয়ে পরীক্ষা না করে।
??প্রশ্ন করে: “nullish?”??-এর প্রশ্ন ভিন্ন — মানটি কিnullবাundefined?0এর কোনোটিই নয়; এটি একটি সত্যিকারের মান।- ঠিক ফল: 0.
??বাম দিকের0-ই রেখে দিল। ব্যবহারকারী যা চেয়েছেন, তাই হলো। - ঘর ফাঁকা থাকলে দুটিই একমত। qty-তে কিছুই না এলে (
undefined) দুটিই দেয়1. পার্থক্য শুধু তখন, যখন বৈধ মানটি falsy —0,""বাfalse. আর ঠিক এখানেই বাগ লুকিয়ে থাকে।
??“মান খারাপ বা ফাঁকা” মানে যেকোনো falsy →
|||| তাহলে কি বাতিল? না — যখন আপনি সত্যিই ফাঁকা string-কেও “নেই” ধরতে চান, তখন ||-ই ঠিক। যেমন নাম: displayName || "অতিথি" — কেউ নামের ঘর ফাঁকা রেখে দিলে “অতিথি” দেখানোই উচিত। প্রশ্নটা নিজেকে করুন: ০ বা ফাঁকা string কি এখানে বৈধ মান? হ্যাঁ হলে ??.
Logical assignment — &&=, ||=, ??= (ES2021)
ES2021 তিনটি সংক্ষিপ্ত রূপ যোগ করেছে, যা logical অপারেটর আর assignment মেলায় — logical assignment:
x ??= 10— x nullish হলে তবেই 10 বসাও। (প্রায়x ?? (x = 10))x ||= 10— x falsy হলে তবেই 10 বসাও।x &&= f(x)— x truthy হলে তবেই নতুন মান বসাও।
এরাও short-circuit করে: শর্ত না মিললে assignment হয়ই না। তাই settings.theme ??= "light" আগে থেকে থাকা theme কখনো মুছবে না। এবার সব একসাথে চালিয়ে দেখুন:
const falsy = [false, 0, -0, 0n, "", null, undefined, NaN];
console.log(falsy.some(v => Boolean(v))); // কোনোটি কি truthy?
console.log(Boolean("0"), Boolean("false"), Boolean([]), Boolean({}));
// || আর && operand ফেরত দেয়, true/false নয়
console.log("রহিম" || "অতিথি");
console.log("" || "অতিথি");
console.log(0 && "কখনো আসবে না");
console.log(5 && "দুটোই truthy");
// ডেলিভারি চার্জ: দোকানদার ইচ্ছে করে 0 দিয়েছেন
const userCharge = 0;
console.log("|| দিয়ে:", userCharge || 60, "| ?? দিয়ে:", userCharge ?? 60);
// logical assignment (ES2021)
const settings = { theme: null, volume: 0 };
settings.theme ??= "light"; // null ছিল → বসল
settings.volume ??= 50; // 0 আছে → বদলাল না
settings.fontSize ||= 16; // ছিলই না → বসল
console.log(settings);প্রথম লাইনে আসে false — তালিকার ৮টির একটিও truthy নয়। দ্বিতীয় লাইনে চারটি true: "0", "false", [], {} সবাই truthy. তারপর রহিম, অতিথি, 0, দুটোই truthy — প্রতিবার একটি operand, কখনো boolean নয়। ডেলিভারির লাইনে || দিয়ে: 60 | ?? দিয়ে: 0 — || দোকানদারের ফ্রি ডেলিভারি মুছে দিল, ?? রেখে দিল। শেষ লাইনে { theme: 'light', volume: 0, fontSize: 16 }: ??= null-কে বদলেছে কিন্তু 0-কে ছোঁয়নি।
৭ · Optional chaining ?. — ভেতরের ডেটা নিরাপদে পড়া
বাস্তব অ্যাপে ডেটা আসে API থেকে, আর সেটি প্রায়ই অসম্পূর্ণ। একটি ডেলিভারি অ্যাপে অর্ডারের ভেতরে ক্রেতা, ক্রেতার ভেতরে ঠিকানা, ঠিকানার ভেতরে জেলা। কোনো অর্ডারে ঠিকানাই দেওয়া নেই। সরাসরি order.customer.address.district লিখলে পাবেন:
TypeError: Cannot read properties of undefined (reading 'district')
আগে এর সমাধান ছিল লম্বা && শিকল: order && order.customer && order.customer.address && order.customer.address.district. ES2020 দিয়েছে optional chaining: ?. — বাম দিকের মান nullish হলে সেখানেই থেমে পুরো expression undefined দেয়, error নয়। আর ??-এর সাথে মিলিয়ে দেওয়া যায় ডিফল্ট:
const district = order?.customer?.address?.district ?? "অজানা";প্রতিটি ?. একটি পাহারাদার: “বাম দিকের জিনিসটা কি null বা undefined? হলে আর এগিও না।” শেষে ?? বলে, “পুরো শিকল থেকে কিছু না পেলে ‘অজানা’ দাও।” এই জোড়াটি — ?. দিয়ে পড়া, ?? দিয়ে ডিফল্ট — আধুনিক JavaScript-এ সবচেয়ে বেশি দেখা প্যাটার্নগুলোর একটি।
| এখন দেখছি | |
| পাওয়া মান | |
| সিদ্ধান্ত |
- order আছে। প্রথম
?.-এর আগেorder— একটি object, nullish নয়। তাই.customerপড়া নিরাপদ। - customer আছে।
order.customer→{ name: "সুমি" }. আবার nullish নয়, তাই পরের ধাপে এগোনো যায়। - address নেই।
customer.address— এমন property নেই, তাই মানundefined. এটি কোনো error নয়; নেই এমন property পড়লে JavaScriptundefinedদেয়। সমস্যা হতো এর পরের ধাপে। - থামা — short-circuit. পরের
?.দেখল বাম দিকundefined. তাই.districtপড়ার চেষ্টাই করল না, আর শিকলের বাকি পুরোটা বাদ দিয়ে ফল দিলundefined. সাধারণ.থাকলে এখানেই TypeError হতো। ??ডিফল্ট দিল। শিকলের ফলundefined, যা nullish — তাই??ডান দিকের"অজানা"ফেরত দিল। জেলা থাকলে (যেমন"রাজশাহী") সেটিই আসত।
?.-এর তিনটি রূপ আছে — property, function কল আর index:
obj?.prop— property পড়া।obj?.[key]বাarr?.[0]— বন্ধনী দিয়ে property বা index পড়া।fn?.()বাobj.method?.()— function থাকলে তবেই ডাকো। দেখতে অদ্ভুত, কিন্তু কারণ আছে:a?.[0]না লিখেa?[0]লিখলে parser ternary-র? :ভেবে বসত।
const orders = [
{ id: 1, customer: { address: { district: "রাজশাহী" } } },
{ id: 2, customer: { name: "সুমি" } },
{ id: 3 },
null,
];
for (const order of orders) {
const district = order?.customer?.address?.district ?? "অজানা";
console.log(order?.id ?? "?", district);
}
const api = { getCoupon: undefined, items: [{ price: 250 }] };
console.log(api.getCoupon?.()); // function নেই → ডাকা হলো না
console.log(api.items?.[0]?.price); // index দিয়ে পড়া
console.log(api.tags?.[0]); // tags-ই নেইআউটপুট: 1 রাজশাহী, 2 অজানা, 3 অজানা, ? অজানা, তারপর undefined, 250, undefined. চতুর্থ অর্ডারটি পুরোটাই null — তবু কোনো error নেই, কারণ প্রথম ?.-ই থেমে গেছে। api.getCoupon?.() function না থাকায় ডাকার চেষ্টাই করেনি। খেয়াল করুন, ?. শুধু null/undefined-এ থামে: বাম দিক 0 বা "" হলে থামে না, স্বাভাবিকভাবে property পড়ে — && শিকলের সাথে এটাই বড় পার্থক্য।
?.-এর সীমা। (১) পাহারা বসে শুধু যেখানে ?. লিখেছেন: user?.profile.city-এ user nullish হলে ঠিক আছে, কিন্তু user থাকলে আর profile না থাকলে আবার TypeError: Cannot read properties of undefined (reading 'city'). (২) ?. দিয়ে মান বসানো যায় না — user?.name = "x" লিখলে V8 দেয় SyntaxError: Invalid left-hand side in assignment. (৩) সব জায়গায় ?. ছিটিয়ে দেবেন না: যে ডেটা অবশ্যই থাকার কথা, সেখানে ?. দিলে আসল বাগ চাপা পড়ে, ভুল অনেক পরে অন্য জায়গায় ধরা পড়ে। ?. শুধু সেখানে, যেখানে “না থাকা” বৈধ অবস্থা।
৮ · আরও কিছু অপারেটর — ternary থেকে bitwise
Ternary — শর্ত ? ক : খ
JavaScript-এর একমাত্র তিন-operand অপারেটর: শর্ত truthy হলে প্রথম মান, নইলে দ্বিতীয়। const fee = isStudent ? 20 : 50; এটি একটি expression, তাই সরাসরি variable-এ বা template literal-এ বসানো যায় — if তা পারে না। দুই স্তর পর্যন্ত ঠিক আছে; তার বেশি নেস্ট করলে পড়া কঠিন — তখন if/switch ভালো (পর্ব ১১)। Ternary ডান দিক থেকে জোড়া লাগে, তাই a ? b : c ? d : e মানে a ? b : (c ? d : e).
টাইপ আর সম্পর্ক যাচাইয়ের অপারেটর
| Operator | কী করে | উদাহরণ → ফল |
|---|---|---|
typeof | মানের টাইপের নাম string হিসেবে | typeof 10n → "bigint"; typeof null → "object" (পুরোনো বাগ, পর্ব ০৬) |
instanceof | object-টি কোনো class/constructor থেকে তৈরি কি না (prototype chain দেখে) | [] instanceof Array → true; [] instanceof Object → true |
in | object-এ (বা তার prototype-এ) এই নামের property আছে কি না | "b" in {b: 1} → true; 1 in [5] → false |
delete | object থেকে একটি property মুছে ফেলে; সফল হলে true | delete user.tempToken |
void | expression চালায়, তারপর সবসময় undefined দেয় | void 0 → undefined (পুরোনো কোডে দেখবেন) |
, (comma) | বাম থেকে সব চালায়, শেষেরটির মান দেয় | (1, 2, 3) → 3; মূলত for (i = 0, j = 9; …)-এ |
in আর ?./??-এর পার্থক্য সূক্ষ্ম: { phone: undefined }-এ "phone" in obj হলো true (property আছে, মান undefined), কিন্তু obj.phone ?? "নেই" দেয় “নেই”। “property আছে কি না” আর “মান আছে কি না” আলাদা প্রশ্ন। আর delete শুধু property মোছে, variable বা মেমরি নয় — মেমরি মোছে garbage collector (পর্ব ০৫)। পর্ব ০৫-এ দেখেছেন, delete object-এর hidden class-ও নষ্ট করতে পারে; property-কে “খালি” বোঝাতে অনেক সময় null বসানোই ভালো।
Bitwise অপারেটর — ৩২টি বাতি
Bitwise অপারেটর সংখ্যাকে দেখে বাইনারি অঙ্কের (bit) সারি হিসেবে, আর প্রতিটি bit আলাদাভাবে মেলায়। একটি গুরুত্বপূর্ণ তথ্য: JavaScript-এর number ৬৪-বিট দশমিক সংখ্যা হলেও bitwise কাজের আগে সংখ্যাটিকে ৩২-বিটের পূর্ণসংখ্যায় বদলে নেওয়া হয় — দশমিক অংশ বাদ, ৩২ বিটের বাইরের অংশ বাদ।
| Operator | নাম | উদাহরণ (5 = 101, 3 = 011) |
|---|---|---|
& | AND — দুটিতেই 1 হলে 1 | 5 & 3 → 1 (001) |
| | OR — যেকোনো একটিতে 1 হলে 1 | 5 | 3 → 7 (111) |
^ | XOR — ঠিক একটিতে 1 হলে 1 | 5 ^ 3 → 6 (110) |
~ | NOT — সব bit উল্টে দেয় | ~5 → -6 |
<< | বাঁয়ে সরানো (২ দিয়ে গুণের মতো) | 1 << 3 → 8 |
>> | ডানে সরানো, চিহ্ন রেখে | -16 >> 2 → -4 |
>>> | ডানে সরানো, বাঁয়ে শূন্য ভরে (unsigned) | -1 >>> 0 → 4294967295 |
৩২-বিটে বদলানোর প্রভাব চোখে দেখা যায়: 3.9 | 0 → 3, 2147483648 | 0 → -2147483648, আর 4294967296 | 0 → 0. পুরোনো কোডে দশমিক কাটতে ~~x বা x | 0 দেখবেন — বড় সংখ্যায় এটি ভুল ফল দেয়, তাই Math.trunc(x) লিখুন।
Bitwise-এর সবচেয়ে বাস্তব ব্যবহার হলো bit flag — একটি সংখ্যার ভেতরে কয়েকটি হ্যাঁ/না তথ্য রাখা। একটি অফিস-অ্যাপে ব্যবহারকারীর অনুমতি ভাবুন: পড়া, লেখা, মোছা। প্রতিটি অনুমতি একটি আলাদা bit — ঘরের সুইচবোর্ডের আলাদা আলাদা সুইচের মতো:
const READ = 1, WRITE = 2, DELETE = 4; // 0b001, 0b010, 0b100
let perm = READ | WRITE; // 3 (011) — পড়া আর লেখা চালু
(perm & WRITE) !== 0; // true — লেখার অনুমতি আছে?
(perm & DELETE) !== 0; // false — মোছার অনুমতি নেই
perm |= DELETE; // 7 (111) — মোছার সুইচ চালু
perm &= ~WRITE; // 5 (101) — লেখার সুইচ বন্ধ
perm.toString(2); // "101"| দিয়ে সুইচ চালু, & ~ দিয়ে বন্ধ, & দিয়ে যাচাই। তিনটি অনুমতি মাত্র একটি সংখ্যায় — ডেটাবেসে বা নেটওয়ার্কে পাঠাতে কম জায়গা লাগে। Unix-এর ফাইল অনুমতি (chmod 755) আর অনেক গেম engine ঠিক এই কৌশল ব্যবহার করে। সাধারণ ওয়েব অ্যাপে অবশ্য { read: true, write: false }-এর মতো object বা Set পড়তে সহজ; bit flag বেছে নিন যখন জায়গা বা গতি সত্যিই গুরুত্বপূর্ণ। আর মনে রাখুন & (bitwise) আর && (logical) সম্পূর্ণ আলাদা — একটি & কম লিখলে কোড চলে, কিন্তু ভুল ফল দেয়।
এক নজরে আগামীর অপারেটর — spread ...
তিনটি বিন্দু ... দেখতে অপারেটরের মতো, কিন্তু আনুষ্ঠানিকভাবে এটি spread syntax: [...a, ...b] দুটি array জুড়ে দেয়, { ...user, city: "খুলনা" } object কপি করে একটি property বদলায়, Math.max(...nums) array-কে আলাদা argument-এ ভেঙে দেয়। পর্ব ১৭-এ এটি বিস্তারিত দেখবেন।
৯ · অগ্রাধিকার আর associativity — কে আগে হিসাব হবে
স্কুলে শিখেছেন: গুণ-ভাগ আগে, যোগ-বিয়োগ পরে। 2 + 3 * 4 = 14, 20 নয়। JavaScript-এর প্রতিটি অপারেটরেরও এমন একটি স্তর আছে — operator precedence. আর একই স্তরের দুটি অপারেটর পাশাপাশি এলে কোন দিক থেকে জোড়া লাগবে, তা ঠিক করে associativity: 10 - 4 - 3 বাম থেকে ((10 - 4) - 3 = 3), কিন্তু 2 ** 3 ** 2 আর a = b = 0 ডান থেকে।
| স্তর (ওপরে = আগে) | অপারেটর | দিক |
|---|---|---|
| সবার আগে | ( … ) বন্ধনী | — |
| property ও কল | . ?. [ ] ( ) new | বাম → ডান |
| postfix | x++ x-- | — |
| unary | ! ~ -x +x ++x typeof void delete await | ডান → বাম |
| ঘাত | ** | ডান → বাম |
| গুণ-ভাগ | * / % | বাম → ডান |
| যোগ-বিয়োগ | + - | বাম → ডান |
| shift | << >> >>> | বাম → ডান |
| তুলনা | < <= > >= in instanceof | বাম → ডান |
| সমতা | === !== == != | বাম → ডান |
| bitwise | &, তারপর ^, তারপর | | বাম → ডান |
| logical AND | && | বাম → ডান |
| logical OR ও nullish | || ?? (একসাথে বন্ধনী ছাড়া নয়) | বাম → ডান |
| শর্ত ও assignment | ? : = += ??= … => | ডান → বাম |
| সবার শেষে | , | বাম → ডান |
পুরো তালিকা মুখস্থ করার দরকার নেই। তিনটি জিনিস মনে রাখুন: গণিতের নিয়ম গণিতের মতোই; তুলনা হয় logical-এর আগে (তাই a > 0 && b > 0-এ বন্ধনী লাগে না); আর && হয় ||-এর আগে। সন্দেহ হলে বন্ধনী দিন — বন্ধনী কখনো ভুল নয়। এবার একটি ক্লাসিক প্রশ্ন ধাপে ধাপে দেখুন:
a || b && c
প্রশ্ন: বাম থেকে ডানে পড়ব — (a || b) && c ?
a || (b && c) || ← মূল অপারেটর (সবার শেষে হিসাব) ├─ a └─ b && c
0 || (b && c) 0 falsy → উত্তর এখনো জানা নেই → ডান দিক লাগবে
0 || ("রহিম" && "") 0 || "" b truthy → && ফেরত দেয় c-কে, অর্থাৎ ""
0 || "" → "" দুটিই falsy → || শেষ operand ফেরত দেয় (a || b) && c ধরলেও এখানে "" আসত — কিন্তু অন্য মানে আলাদা হবে
- প্রশ্নটা কী?
a || b && c— বাম থেকে ডানে পড়লে মনে হয় আগেa || b. কিন্তু JavaScript বাম থেকে পড়ে না; সে আগে দেখে কোন অপারেটরের অগ্রাধিকার বেশি। &&আগে জোড়া লাগে। গুণ যেমন যোগের আগে,&&তেমন||-এর আগে। তাই expression আসলেa || (b && c)— একটি গাছ, যার মূলে||. অগ্রাধিকার ঠিক করে কে কার operand; হিসাবের ক্রম নয়।- তবু বাম দিক আগে হিসাব। মূল
||প্রথমে তার বাম operand দেখে:a=0, falsy. “অথবা”-র উত্তর এখনো জানা নেই, তাই ডান দিক হিসাব করতে হবে। (a truthy হলেb && cচালানোই হতো না।) - ডান দিকের
&&.b="রহিম", truthy. “এবং”-এর উত্তর তাই নির্ভর করেc-এর ওপর, আর&&ফেরত দেয়cনিজেকে —"". - ফল
"". এখন বাকি0 || "". দুটিই falsy, তাই||শেষ operand""ফেরত দেয়। ফলtrue/falseনয়, একটি ফাঁকা string — আর এটি নিজে falsy. এমন কোডে বন্ধনী দিয়ে উদ্দেশ্য স্পষ্ট করুন।
??-কে || বা &&-এর সাথে বন্ধনী ছাড়া মেশানো যায় না
a ?? b || c — এর মানে কী হওয়া উচিত? (a ?? b) || c, নাকি a ?? (b || c)? দুটিই যুক্তিসঙ্গত, আর ভুল বেছে নিলে চুপচাপ বাগ। তাই TC39 সিদ্ধান্ত নিয়েছে: অনুমান নয়, error. Chrome বা Node.js-এ লিখে দেখুন:
null ?? 0 || 1 → SyntaxError: Unexpected token '||'
0 || null ?? 5 → SyntaxError: Unexpected token '??'
সমাধান: বন্ধনী — (0 || null) ?? 5 দেয় 5. এটি SyntaxError, তাই parse করার সময়েই ধরা পড়ে: ফাইলের একটি লাইনও চলবে না (পর্ব ০৫)। &&-এর সাথেও একই নিয়ম (Unexpected token '&&'). তৃতীয় ভাবনার প্রশ্নে এই সিদ্ধান্তের পেছনের যুক্তি আছে।
গাণিতিক, তুলনা, bitwise আর ternary প্রায় C/Java-র মতোই; Java-র মতো JavaScript-এও >>> আছে, আর %-এর চিহ্ন ভাজ্যের মতো। বড় পার্থক্য: C/C++/Java-তে &&/|| শুধু boolean (C-তে 0/1) দেয়, Java তো boolean ছাড়া operand নেয়ই না (Java-র অপারেটর)। Python-এর and/or ঠিক JavaScript-এর মতো operand ফেরত দেয়, কিন্তু Python-এ -7 % 3 = 2, আর ??/?. নেই (Python-এর অপারেটর)। আর JavaScript-এর / সবসময় দশমিক দেয় — 7 / 2 হলো 3.5.
- JavaScript-এ falsy মান কোনগুলো? — ৮টি:
false,0,-0,0n,"",null,undefined,NaN;"0",[],{}truthy. ||আর??-এর পার্থক্য? —||যেকোনো falsy বাম দিকে ডান দিক দেয়;??শুধুnull/undefined-এ। তাই0 || 5= 5, কিন্তু0 ?? 5= 0."" && "x"-এর মান কী? —"";&&প্রথম falsy operand ফেরত দেয়, boolean নয়।3 > 2 > 1কেনfalse? — বাম থেকে:3 > 2=true, তারপরtrue > 1→1 > 1=false.- Short-circuit evaluation কী? — বাম দিক দেখেই ফল জানা গেলে ডান দিক চালানো হয় না;
user && user.nameএজন্যই null-এ error দেয় না।
১০ · AI-এর যুগে — ডিফল্ট, শিকল আর অগ্রাধিকারের ভুল ধরা
AI কোড লেখে লক্ষ লক্ষ পুরোনো উদাহরণ দেখে শিখে। ২০২০-এর আগের JavaScript-এ ?? আর ?. ছিল না, তাই সেই সময়ের কোডে সব জায়গায় || আর && শিকল। AI এই অভ্যাসগুলো এখনো প্রায়ই নকল করে। কোড দেখতে ঠিক, চলেও — শুধু ০ বা ফাঁকা string এলে চুপচাপ ভুল করে।
| AI যা প্রায়ই লেখে | সমস্যা | ভালো রূপ |
|---|---|---|
const qty = input.qty || 1; | ০ দিলে ১ হয়ে যায় | input.qty ?? 1 |
price = price || 0; | ক্ষতি নেই মনে হয়, কিন্তু NaN চুপচাপ ০ হয়ে বাগ লুকায় | আগে যাচাই: Number.isFinite(price) |
res && res.data && res.data.items | লম্বা, আর ""/0-এ থেমে সেই মানটিই দেয় | res?.data?.items |
a ?? b || c (বন্ধনী ছাড়া) | SyntaxError — পুরো ফাইল চলবে না | (a ?? b) || c বা a ?? (b || c) |
if (items) দিয়ে “তালিকা ফাঁকা নয়” যাচাই | [] truthy — শর্ত সবসময় সত্য | if (items?.length) |
সব property-তে ?. | অবশ্যই থাকার কথা এমন ডেটার বাগ চাপা পড়ে | শুধু যেখানে “না থাকা” বৈধ |
AI-কে কোড লিখতে বলার সময় এই ফাঁদগুলো আগেই নিয়ম হিসেবে দিয়ে দিন, আর তাকে নিজের কোড যাচাই করতে বলুন:
AI-এর লেখা কোডে অপারেটর — যাচাইয়ের চেকলিস্ট
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন। এগুলোই আপনাকে কোড-লেখক থেকে সিস্টেম-চিন্তক বানাবে।
প্র ০১C বা Java-তে && আর || boolean দেয়, অথচ JavaScript-এ operand ফেরত দেয়। এই নকশা কোথা থেকে এল, আর এটি কী কী কৌশলের জন্ম দিয়েছে?
“অথবা” মানে প্রথম যে মান সত্য, সেটিই উত্তর — এই চিন্তা JavaScript-এর অনেক আগের। Lisp পরিবারের ভাষায় (or nil 5) দেয় 5, আর and দেয় শেষ মানটি — সেখানে “মিথ্যা” মানে শুধু nil, বাকি সব সত্য। ১৯৮০-র দশকের শেষে আসা Perl-এ $name = $input || "guest"; আর open(...) or die ছিল প্রতিদিনের কৌশল। পরে Python-এর and/or আর Ruby-র ||=-ও একই পথে হেঁটেছে। ১৯৯৫ সালে JavaScript-এর বাক্যগঠন C/Java-র মতো দেখতে হলেও এই অংশে আচরণটি এসেছে এই “dynamic” ভাষাগুলোর ধারা থেকে — যেখানে টাইপ আগে থেকে জানা নেই, আর যেকোনো মানকে শর্ত হিসেবে ব্যবহার করা স্বাভাবিক।
এই নকশা কয়েকটি বহুল প্রচলিত কৌশলের জন্ম দেয়। ES2015-এর আগে function-এ ডিফল্ট parameter ছিল না, তাই লেখা হতো function connect(options) { options = options || {}; }. পুরো লাইব্রেরি এক নামে রাখার “namespace” কৌশল ছিল var MyApp = MyApp || {}; — একাধিক script একই object-এ যোগ করতে পারত। পাহারা বসানো হতো user && user.name দিয়ে, আর ঐচ্ছিক callback ডাকা হতো callback && callback() দিয়ে। কোড ছোট হতো, আর বহু বছর এটাই ছিল “idiomatic JavaScript”.
কিন্তু দামও ছিল। “মান নেই” আর “মান falsy” এক হয়ে গেল। ০ দাম, ফাঁকা নাম, false সেটিং — সবকিছু চুপচাপ ডিফল্টে বদলে যেত। এই বাগ এত সাধারণ ছিল যে ভাষাকেই বদলাতে হয়েছে: ES2015-এর ডিফল্ট parameter (function f(x = 10)) কাজ করে শুধু undefined-এ, falsy-তে নয়; আর ES2020 যোগ করে ?? — অর্থাৎ ভাষা নিজেই স্বীকার করেছে যে ডিফল্টের প্রশ্ন আসলে “nullish?”, “falsy?” নয়।
আরেকটি সূক্ষ্ম সুবিধা আছে: operand ফেরত দেওয়ার কারণে || আর && দিয়ে একটি বাড়তি রূপান্তর ছাড়াই মান বাছাই করা যায় — engine-কে নতুন boolean বানাতে হয় না। if-এর ভেতরে ফল আবার truthy হিসেবে দেখা হয়, তাই শর্তের ক্ষেত্রে কোনো পার্থক্যও নেই। পার্থক্য দেখা দেয় শুধু তখন, যখন ফলটি কোথাও রাখা হয় বা দেখানো হয়।
মূল উপলব্ধি: একটি ভাষার নকশা-সিদ্ধান্ত বহু দশক ধরে কৌশল আর বাগ — দুটিই তৈরি করে। || এখনো কাজের, কিন্তু এর আসল প্রশ্নটা মনে রাখুন: “এটা কি truthy?” আপনার প্রশ্ন যদি “এটা কি আছে?” হয়, তাহলে উত্তর ??.
প্র ০২Optional chaining ?. ES2020-এ এসেছে, অথচ ধারণাটি অনেক পুরোনো। CoffeeScript থেকে TC39 পর্যন্ত এই লম্বা পথটা কেমন ছিল?
“null হলে থেমে যাও” ধরনের অপারেটর বহু ভাষায় আগে থেকেই ছিল — Groovy-তে “safe navigation” ?., পরে Swift (২০১৪)-এর optional chaining আর C# 6 (২০১৫)-এর null-conditional ?.. কিন্তু JavaScript-এর জগতে এর জনপ্রিয়তা আসে CoffeeScript থেকে। Jeremy Ashkenas ২০০৯ সালে CoffeeScript প্রকাশ করেন — একটি ছোট ভাষা যা JavaScript-এ রূপান্তরিত হয়। এর “existential operator” ? দিয়ে লেখা যেত user?.address?.city; null বা undefined পেলে পুরো শিকল চুপচাপ undefined দিত। হাজার হাজার ডেভেলপার এটি ব্যবহার করে বুঝলেন, এটি ছাড়া API-র ডেটা পড়া কতটা কষ্টের।
তারপরও JavaScript-এ আসতে প্রায় দশ বছর লেগেছে। কারণ ভাষায় নতুন বাক্যগঠন যোগ করা কঠিন — কোটি কোটি পাতার পুরোনো কোড যেন না ভাঙে। TC39-এ প্রস্তাবটি কয়েক বছর প্রাথমিক স্তরে আটকে ছিল। বিতর্ক ছিল কয়েকটি প্রশ্নে: (১) বাক্যগঠন — ? আগে থেকেই ternary-র অংশ। a?[0]:1 কি ternary, না optional index? তাই শেষে বেছে নেওয়া হয় ?., আর index ও কলের জন্য দেখতে খানিকটা অদ্ভুত ?.[ ] ও ?.( ). এমনকি a?.5:1 যেন আগের মতোই ternary থাকে (অর্থাৎ a ? .5 : 1), সেজন্য নিয়ম হলো ?.-এর পরে অঙ্ক এলে সেটি optional chaining নয়। (২) short-circuit কতদূর — একটি ধাপ, নাকি পুরো শিকল? শেষে সিদ্ধান্ত হয় পুরো শিকল বাদ যাবে, যাতে a?.b.c-এ a nullish হলে .c-তেও error না আসে।
প্রস্তাবকদের মধ্যে ছিলেন Gabriel Isenberg, Claude Pache ও Dustin Savery. ২০১৯ সালে প্রস্তাবটি দ্রুত এগোয় এবং বছরের শেষে চূড়ান্ত স্তরে (stage 4) পৌঁছায়; ES2020-এ ??-এর সাথে একসাথে মানদণ্ডে যুক্ত হয়। TypeScript 3.7 (২০১৯-এর নভেম্বর) আর Babel আগেই এটি চালু করে দেওয়ায় অনেকে মানদণ্ডের আগেই ব্যবহার শুরু করেছিলেন, আর ২০২০ সালের শুরু থেকে প্রধান ব্রাউজারগুলো সরাসরি সমর্থন দিতে থাকে।
এই গল্পে TC39-এর কাজের ধরনও দেখা যায়: কোনো ফিচার “stage 0” থেকে ধাপে ধাপে “stage 4” পর্যন্ত যায়, আর শেষ ধাপে পৌঁছাতে মানদণ্ডের অফিসিয়াল টেস্ট (test262) আর অন্তত দুটি স্বাধীন বাস্তবায়ন লাগে। CoffeeScript-এর মতো ভাষা আর Babel-এর মতো টুল তাই আসলে একধরনের পরীক্ষাগার — নতুন ধারণা সেখানে পরীক্ষিত হয়ে তারপর ভাষায় আসে।
মূল উপলব্ধি: একটি তিন অক্ষরের অপারেটরের পেছনেও দশ বছরের নকশা-বিতর্ক থাকতে পারে। ভাষায় যা একবার ঢোকে, তা আর বের করা যায় না — তাই TC39 ধীরে, কিন্তু সাবধানে এগোয়।
প্র ০৩a ?? b || c কেন SyntaxError? অন্য অপারেটরগুলো তো অগ্রাধিকারের নিয়মে মিশে যায় — ??-এর বেলায় TC39 আলাদা পথ নিল কেন?
অগ্রাধিকারের নিয়ম কাজ করে যখন সবাই নিয়মটা জানে ও স্বাভাবিক মনে করে। গুণ যোগের আগে — এটি স্কুল থেকে সবার মাথায় আছে। কিন্তু ?? নতুন অপারেটর; এর সাথে || মেশালে মানুষ কী আশা করবে, তার কোনো স্বাভাবিক উত্তর নেই। a ?? b || c দেখে কেউ ভাববেন “a না থাকলে b অথবা c”, কেউ ভাববেন “a ?? b-এর ফল falsy হলে c”. দুটি অর্থ ভিন্ন ফল দেয়, আর ভুল অনুমান কোনো error ছাড়াই চুপচাপ ভুল করে।
প্রস্তাবের আলোচনায় বিকল্পগুলো ছিল: ??-কে ||-এর চেয়ে বেশি অগ্রাধিকার দেওয়া, কম দেওয়া, অথবা মেশানোই নিষেধ করা। TC39 বেছে নেয় শেষেরটি। মানদণ্ডের ব্যাকরণে ??-এর শিকল (CoalesceExpression) এমনভাবে লেখা যে তার operand-এ বন্ধনী ছাড়া && বা || থাকতে পারে না। ফলে parser সরাসরি SyntaxError: Unexpected token '||' দেয় (V8-এর বার্তা)। যুক্তিটা সহজ: পরে যদি দেখা যায় একটি অগ্রাধিকার স্বাভাবিক, error সরিয়ে সেটি যোগ করা যাবে — পুরোনো কোড ভাঙবে না, কারণ সেই কোড আগে চলতই না। কিন্তু একবার একটি অগ্রাধিকার ঠিক করে দিলে আর বদলানো যায় না।
আরেকটি বাস্তব কারণ ছিল পুরোনো কোডের অভ্যাস। বহু বছর ধরে মানুষ a || b লিখেছেন ডিফল্টের জন্য। কেউ যখন ধীরে ধীরে কোড বদলাবেন, একটি লাইনে || আর ?? মিশে যাওয়ার সম্ভাবনা বেশি — আর ঠিক তখনই অস্পষ্টতা সবচেয়ে বিপজ্জনক। Error দিলে ডেভেলপার থামতে বাধ্য হন এবং নিজের উদ্দেশ্য বন্ধনী দিয়ে লিখে দেন।
অন্য ভাষাগুলো ভিন্ন সিদ্ধান্ত নিয়েছে — C#-এ ??-এর নির্দিষ্ট অগ্রাধিকার আছে, বন্ধনী ছাড়াও মেশানো যায়। এটি দেখায় যে এখানে “একমাত্র সঠিক উত্তর” নেই; প্রশ্নটা ছিল কোন ভুল বেশি ক্ষতিকর — একটি বিরক্তিকর SyntaxError, নাকি চুপচাপ ভুল ফল। JavaScript বেছে নিয়েছে প্রথমটি। একই দর্শন দেখবেন -2 ** 2-এর ক্ষেত্রে (প্র ০৫)।
মূল উপলব্ধি: ভালো ভাষা-নকশা কখনো কখনো “বুদ্ধিমান অনুমান” না করে স্পষ্টতা দাবি করে। আপনার নিজের কোডেও একই নীতি কাজে লাগে: অস্পষ্ট হলে বন্ধনী দিন — compiler-এর জন্য নয়, মানুষের জন্য।
প্র ০৪JavaScript-এর সব number ৬৪-বিট দশমিক, অথচ bitwise অপারেটর কাজ করে ৩২-বিট পূর্ণসংখ্যায়। এই অদ্ভুত মিশ্রণ কেন, আর asm.js-এর |0 কৌশল কী ছিল?
১৯৯৫ সালে JavaScript-কে সরল রাখতে সংখ্যার একটিই টাইপ রাখা হয়: IEEE 754 মানের ৬৪-বিট “double”. নতুন প্রোগ্রামারের জন্য এটি আরামদায়ক — int, long, float-এর পার্থক্য শিখতে হয় না। কিন্তু ভাষার বাক্যগঠন নেওয়া হয়েছিল C/Java থেকে, আর সেখানে &, |, << ছিল পূর্ণসংখ্যার অপারেটর। দশমিক সংখ্যার bit নিয়ে খেলার কোনো স্বাভাবিক অর্থ নেই। তাই মানদণ্ড একটি নিয়ম দিল: bitwise কাজের আগে মানটিকে ToInt32 (আর >>>-এর জন্য ToUint32) দিয়ে ৩২-বিটের two's complement পূর্ণসংখ্যায় বদলাও, কাজ শেষে আবার number বানাও। এজন্যই 2147483648 | 0 হয়ে যায় -2147483648 — ৩২তম bit চিহ্নের bit হয়ে গেছে।
এই “পার্শ্বপ্রতিক্রিয়া” পরে একটি চতুর কৌশলে কাজে লাগে। ২০১৩ সালে Mozilla প্রকাশ করে asm.js — JavaScript-এরই একটি কড়া উপসেট, যাতে C/C++ থেকে রূপান্তরিত কোড প্রায় native গতিতে চলে। সমস্যা ছিল, JavaScript-এ টাইপ লেখার উপায় নেই। সমাধান: x | 0. এর ফল সবসময় ৩২-বিট পূর্ণসংখ্যা — তাই engine নিশ্চিতভাবে জানে “এই variable int”. একইভাবে +x মানে “double”. যে engine asm.js চিনত না, তার কাছে এগুলো সাধারণ, বৈধ JavaScript — ফল একই, শুধু ধীর। যে চিনত, সে আগেভাগেই পুরোটা machine code-এ compile করত। Emscripten টুল দিয়ে এভাবে বড় বড় C++ গেম engine ব্রাউজারে চালানো হয়েছিল। এই অভিজ্ঞতা থেকেই পরে এসেছে WebAssembly (পর্ব ০৫-এর ভাবনার প্রশ্ন)।
ভাষা নিজেও ৩২-বিট পূর্ণসংখ্যার জন্য কিছু সাহায্য যোগ করেছে: ES2015-এর Math.imul (৩২-বিট গুণ, যেখানে সাধারণ * দশমিক নির্ভুলতা হারাতে পারে) আর Math.clz32. আর ES2020-এর BigInt যেকোনো আকারের পূর্ণসংখ্যায় bitwise চালাতে দেয় — 1n << 100n সম্পূর্ণ বৈধ। শুধু >>> BigInt-এ নেই, কারণ অসীম আকারের সংখ্যায় “unsigned” ধারণার মানে নেই; চালালে V8 দেয় TypeError: BigInts have no unsigned right shift, use >> instead. BigInt নিয়ে বিস্তারিত পর্ব ১০-এ।
এর ব্যবহারিক শিক্ষা: পুরোনো কোডের ~~x বা x | 0 দিয়ে দশমিক কাটা ২৩১-এর বড় সংখ্যায় ভুল দেয় — ~~3000000000 হয় -1294967296. টাকার হিসাব বা টাইমস্ট্যাম্পের মতো বড় সংখ্যায় এই ফাঁদ বাস্তব। Math.trunc বা Math.floor ব্যবহার করুন।
মূল উপলব্ধি: সরলতার জন্য নেওয়া একটি সিদ্ধান্ত (একটিই number টাইপ) অন্য জায়গায় জটিলতা তৈরি করে, আর প্রকৌশলীরা সেই জটিলতাকেই পরে কৌশলে পরিণত করেন। কোনো অদ্ভুত আচরণ দেখলে তার ইতিহাস খুঁজুন — প্রায়ই সেখানে একটি যুক্তিসঙ্গত কারণ মেলে।
প্র ০৫-2 ** 2 লিখলে JavaScript হিসাব না করে SyntaxError দেয়। Python বা Excel তো ফল দেয় — JavaScript এত কড়া কেন?
সমস্যাটা গণিতের লেখার ধরনে। গণিতের বইয়ে −2² মানে −(2²) = −4: ঘাত আগে, ঋণাত্মক চিহ্ন পরে। Python এই নিয়ম মানে — -2**2 দেয় -4. কিন্তু Microsoft Excel-এ =-2^2 লিখলে আসে 4, কারণ Excel-এ ঋণাত্মক চিহ্নের অগ্রাধিকার ঘাতের চেয়ে বেশি। অর্থাৎ একই লেখা দুটি বহুল ব্যবহৃত টুলে দুটি ভিন্ন ফল দেয়। প্রোগ্রামারদের মধ্যেও অনেকে ধরে নেন unary অপারেটর সবসময় আগে বসে — JavaScript-এ -x * y, !a && b সবখানেই তাই।
ঘাতের অপারেটর ** যুক্ত হয় ES2016-এ (তার আগে শুধু Math.pow). প্রস্তাব নিয়ে আলোচনার সময় TC39 এই দ্বিধা নিয়ে বিতর্ক করে। JavaScript-এর সাধারণ নিয়ম মানলে unary minus আগে বসত, আর -2 ** 2 হতো 4 — গণিত-জানা মানুষের কাছে বিস্ময়কর। গণিতের নিয়ম মানলে -4 — কিন্তু তাহলে JavaScript-এর নিজের অগ্রাধিকার-টেবিলে একটি ব্যতিক্রম ঢুকত। শেষে সিদ্ধান্ত হয়: কোনোটিই নয়। ব্যাকরণে লেখা হলো, **-এর বাম দিকে বন্ধনী ছাড়া unary অপারেটর থাকতে পারবে না। তাই -2 ** 2, typeof 2 ** 2, !2 ** 2 — সবই SyntaxError, বার্তা: “Unary operator used immediately before exponentiation expression. Parenthesis must be used to disambiguate operator precedence” (V8).
খেয়াল করুন, সমস্যা শুধু বাম দিকে। 2 ** -1 সম্পূর্ণ বৈধ (0.5), কারণ সেখানে কোনো দ্বিধা নেই। আর associativity-তে ** গণিতের নিয়মই মেনেছে: 2 ** 3 ** 2 = 2 ** 9 = 512 — ঘাতের টাওয়ার ওপর থেকে নিচে হিসাব হয়, ঠিক যেভাবে 232 পড়া হয়। এটি JavaScript-এর একমাত্র ডান-থেকে-বাম জোড়া লাগা গাণিতিক অপারেটর।
এখানে প্র ০৩-এর মতোই দর্শন কাজ করেছে। দুটি যুক্তিসঙ্গত অর্থ থাকলে ভাষা অনুমান করে না, আপনাকে লিখে দিতে বলে। এর একটি বাড়তি সুবিধা আছে: ভবিষ্যতে TC39 চাইলে একটি অর্থ বেছে নিয়ে এই লেখাকে বৈধ করতে পারবে, কোনো পুরোনো কোড না ভেঙে। কিন্তু উল্টোটা — একবার একটি অর্থ দিয়ে ফেলে পরে বদলানো — কখনো সম্ভব নয়, কারণ ওয়েবের পুরোনো পাতা ভাঙা যায় না (“don't break the web” — TC39-এর মূল নীতি)।
মূল উপলব্ধি: SyntaxError সবসময় শত্রু নয়। কখনো কখনো এটি ভাষার নকশাকারদের পক্ষ থেকে সুরক্ষা — “এখানে মানুষ ভুল বোঝে, তাই স্পষ্ট করে লিখুন।” টাকার হিসাব, বৈজ্ঞানিক সূত্র বা পরীক্ষার নম্বরের হিসাবে ঋণাত্মক সংখ্যার ঘাত এলে (-x) ** n বা -(x ** n) লিখে নিজের উদ্দেশ্য স্পষ্ট করুন।
অনুশীলন
প্রথমে খাতায় উত্তর লিখুন, তারপর ব্রাউজারের console-এ চালিয়ে মেলান।
-
ফল অনুমান করুন: (ক)
null || 0 || ""(খ)1 && "a" && 0(গ)"" ?? "ডিফল্ট"(ঘ)NaN ?? 5(ঙ)!!"false"(ক)
""— তিনটিই falsy, তাই||শেষ operand দেয়। (খ)0—1আর"a"truthy, তাই&&এগোতে থাকে এবং প্রথম falsy মান0ফেরত দেয়। (গ)""— ফাঁকা string nullish নয়, তাই??সেটিই রাখে। (ঘ)NaN— NaN falsy হলেও nullish নয়। (ঙ)true—"false"পাঁচ অক্ষরের string, তাই truthy. (গ) আর (ঘ)-তে||থাকলে ফল হতো যথাক্রমে “ডিফল্ট” আর5. -
বাগ খুঁজুন: একটি মিউজিক প্লেয়ারে লেখা আছে
function setVolume(v) { volume = v || 50; }. ব্যবহারকারী শব্দ পুরো বন্ধ করতে স্লাইডার ০-তে নিলেন। কী হবে, আর কীভাবে ঠিক করবেন?0falsy, তাইv || 50দেয়50— শব্দ বন্ধ না হয়ে মাঝারি ভলিউমে বাজতে থাকবে। ঠিক রূপ:volume = v ?? 50;— শুধু v না দিলে (undefined) বাnullহলে ৫০. আরও ভালো হলো ডিফল্ট parameter:function setVolume(v = 50), যা শুধুundefined-এ কাজ করে। ০ থেকে ১০০-এর বাইরের মান আটকাতে চাইলে আলাদা যাচাইও যোগ করুন। -
শিকল ছোট করুন:
const first = res && res.data && res.data.items && res.data.items[0];—?.দিয়ে লিখুন। তারপর ভাবুন:res.dataযদি""(ফাঁকা string) হয়, দুটি রূপের ফল কি একই?নতুন রূপ:
const first = res?.data?.items?.[0];. ফল সবসময় এক নয়।res.data=""হলে&&শিকল সেখানেই থেমে""দেয়। আর?.থামে না (""nullish নয়), পড়ে"".items→undefined, তারপর পরের?.থেমে শেষ ফলundefined. অর্থাৎ&&“falsy”-তে থামে,?.শুধু “nullish”-এ। বেশিরভাগ ক্ষেত্রে?.-এর আচরণই কাঙ্ক্ষিত। -
Bit flag পড়ুন:
READ = 1,WRITE = 2,DELETE = 4. একজন ব্যবহারকারীরperm = 6. তার কোন কোন অনুমতি আছে?perm & READ-এর মান কত? READ যোগ করতে কী লিখবেন?৬ = বাইনারি
110= 4 + 2, অর্থাৎ WRITE আর DELETE আছে, READ নেই — অদ্ভুত অবস্থা (পড়তে না পারলেও মুছতে পারে), যা বাস্তবে একটি ভুল কনফিগারেশন নির্দেশ করে।perm & READ=110 & 001=0, তাই(perm & READ) !== 0হলোfalse. READ যোগ করতেperm |= READ;— ফল7(111).
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমJavaScript-এর অপারেটরগুলো অগ্রাধিকার অনুযায়ী সাজান — সবচেয়ে আগে হিসাব হয় যেটি, সেটি ওপরে (▲▼ চেপে সরান)
**(ঘাত)*/%(গুণ-ভাগ)+-(যোগ-বিয়োগ)<>=(তুলনা)===!==(সমতা)&&(logical AND)
JavaScript-এ নিচের কোন মানটি truthy?
0- ফাঁকা string
"" "0"(শূন্য লেখা string)NaN
const charge = 0; হলে charge || 60 আর charge ?? 60-এর মান যথাক্রমে কত?
60আর6060আর00আর60falseআর0
JavaScript-এ -2 ** 2 লিখলে কী হয়?
-44NaN- SyntaxError
JavaScript-এ 2 ** 3 ** 2-এর মান কত?
- 512
- 64
- 12
- SyntaxError
JavaScript-এ "10" < "9"-এর মান কী, আর কেন?
false— কারণ 10 বড় সংখ্যাtrue— কারণ অক্ষর ধরে তুলনা হয়, আর "1" < "9"false— কারণ "10"-এ বেশি অক্ষর- TypeError — string তুলনা করা যায় না
const order = null; হলে order?.customer?.city-এর ফল কী?
TypeError: Cannot read properties of nullnullundefined- ফাঁকা string
""
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- Operator মান মিলিয়ে নতুন মান দেয়।
/সবসময় দশমিক দেয়,%-এর চিহ্ন বাম দিকের সংখ্যার মতো,**ডান থেকে জোড়া লাগে আর-2 ** 2SyntaxError. - ৮টি falsy:
false,0,-0,0n,"",null,undefined,NaN. বাকি সব —"0",[],{}-সহ — truthy. &&/||operand ফেরত দেয় আর short-circuit করে:||প্রথম truthy,&&প্রথম falsy (নইলে শেষটি); ডান দিক দরকার না হলে চলেই না।- ডিফল্টে
??, ভেতরের ডেটায়?.:a?.b?.c ?? "অজানা".??-কে||/&&-এর সাথে মেশাতে বন্ধনী বাধ্যতামূলক। - অগ্রাধিকার: গণিত → তুলনা → সমতা →
&&→||/??→ ternary/assignment. সন্দেহ হলে বন্ধনী দিন; bitwise কাজ হয় ৩২-বিট পূর্ণসংখ্যায়।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ০৯ · string, template literal আর বাংলা Unicodeপরবর্তী পর্বString তুলনা, UTF-16 code unit আর বাংলা লেখার খুঁটিনাটি।
- JavaScript প্রোগ্রামিং · কোর্স হোম৫৯টি পর্বপুরো কোর্সের রোডম্যাপ — কোন পর্ব কখন।
- Python-এর অপারেটরতুলনা করুন
and/or-ও operand ফেরত দেয় — কিন্তু%আর ভাগের নিয়ম আলাদা। - Java-র অপারেটরতুলনা করুনকড়া টাইপের ভাষায় logical অপারেটর, integer ভাগ আর
>>>.