Module ০ · JavaScript জিনিসটা কী, কোথায় চলে, আর engine এটা চালায় কীভাবে
engine তোমার code চালায় কীভাবে: parse, bytecode, JIT
এই lesson-এ যা শিখবে
- তিনটা JavaScript engine-এর নাম বলতে পারবে, আর কোনটা কোন browser বা runtime-এ থাকে সেটাও।
- এক লাইন code-এর পিছু নিতে পারবে: লেখা থেকে parse tree, সেখান থেকে bytecode, তারপর machine code।
- একটা SyntaxError আর একটা ReferenceError পড়ে বলতে পারবে, কোনটা কোন ধাপ থেকে এসেছে।
David দশ লাইনের একটা program লেখে, যেটা বিক্রির ছোট একটা report ছাপে। প্রথম লাইনটা console.log("Starting the report..."), তাই সে ধরে নেয় অন্তত ওইটুকু তো আসবে। Run চাপে, আর কিছুই ছাপে না। Playground বলে "Compile error", দেখায় SyntaxError: Unexpected token ')', আর দোষ দেয় লাইন 10-কে।
লাইন 10 তো শেষ লাইন। শেষ লাইন কীভাবে প্রথম লাইনকে চলতে দেয় না? Engine তো তখনো লাইন 10 পর্যন্ত পৌঁছায়ইনি। নাকি পৌঁছেছিল?
পৌঁছেছিল। একটা লাইনও চালানোর আগে engine পুরো file-টা পড়ে নেয়। এই lesson-এ আমরা এক লাইন code-এর পিছু নেব, আর দেখব engine ওটার সঙ্গে কী কী করে। শেষে David-এর ফাঁকা output-এর মানে বুঝবে। সামনে যত error message পাবে, তার বেশিরভাগের মানেও বুঝবে।
Engine কী
Engine হলো সেই program, যেটা JavaScript-এর লেখা পড়ে আর চালায়। Lesson 02-এ এটাকেই বলা হয়েছিল চার socket-এর মাঝখানের বাক্স। হিসাবে ধরার মতো engine আছে তিনটা, প্রতিটা আলাদা প্রতিষ্ঠানের বানানো।
| Engine | কে বানিয়েছে | কোথায় দেখা পাবে |
|---|---|---|
| V8 | Chrome, Edge, Node.js, Deno আর Playground | |
| SpiderMonkey | Mozilla | Firefox |
| JavaScriptCore | Apple | Safari আর Bun runtime |
তিনটাই একই নিয়মের বই মেনে চলে, যার নাম ECMAScript। তাই তোমার code তিনটাতেই একইভাবে চলে। ভেতরে ওদের গড়ন আলাদা, অংশগুলোর নামও আলাদা। এই lesson V8-এর নামগুলো ব্যবহার করে, কারণ তোমার Playground-এর code V8-ই চালায়।
তাই engine একটা program, আর প্রায় সব browser আর server-এর JavaScript চালায় এই তিনটা।
ধাপ 1: parser আগে পুরো file পড়ে
Engine-এর প্রথম অংশটা হলো parser। সে তোমার লেখা পড়ে, আর দেখে সেটা JavaScript-এর ব্যাকরণ মানছে কি না। কিছু চলার আগেই সে পুরো file-এর জন্য এই কাজটা করে।
Parser কাজটা করে দুইটা ছোট ধাপে। প্রথমে লেখাটা কেটে tokens বানায়, মানে ভাষার শব্দ আর চিহ্নগুলো: return, price, *, count, ;। তারপর token-গুলো সাজিয়ে একটা parse tree বানায়, লাইনটার মানে কী তার একটা ছবি।
File-এর কোথাও token দিয়ে ঠিকঠাক tree না বানানো গেলে parser থেমে যায়, আর একটা SyntaxError জানায়। Syntax error মানে "এটা ব্যাকরণ মেনে লেখা JavaScript না"। তখনো কিছুই চলেনি, তাই কিছুই ছাপে না। David-এর সঙ্গে ঠিক এটাই হয়েছে।
তাই SyntaxError আসে ধাপ 1 থেকে, আর সেটা শুধু নিজের লাইন না, পুরো file থামিয়ে দেয়।
ধাপ 2: bytecode, যেটা চালায় একটা interpreter
এরপর engine tree-টাকে বানায় bytecode: খুব ছোট আর সহজ কিছু instruction-এর একটা তালিকা, JavaScript statement-এর চেয়ে অনেক ছোট। V8-এর যে অংশ bytecode বানায় আর চালায়, তার নাম Ignition। এটা একটা interpreter, মানে এমন একটা program, যেটা একটা একটা করে instruction চালায়।
সত্যিকারের bytecode তুমি নিজেও দেখতে পারো। নিজের মেশিনে একটা flag দিলে Node.js এটা ছেপে দেয়। ছবি 1-এর লাইনটা নিচের Example 1-এর ছোট function-এর ভেতরে রেখে চালালে Node 22 এটা ছেপেছিল:
Ldar a1
Mul a0, [0]
Return
তিনটা instruction। Ldar a1 দ্বিতীয় argument, মানে count, তুলে রাখে accumulator নামের একটা কাজের খোপে। Mul a0, [0] সেটাকে প্রথম argument price দিয়ে গুণ করে। Return ফলটা ফেরত দেয়। [0] দেখিয়ে দেয় একটা খোপ, যেখানে engine লিখে রাখে সে কোন type-এর মান দেখেছে। ধাপ 3-এ এটা কাজে লাগে।
নিচের widget এক লাইনকে পুরো যাত্রাটা ধাপে ধাপে ঘুরিয়ে দেখায়। এক ধাপ থেকে পরের ধাপে যেতে step button চাপো।
তাই bytecode হলো engine-এর নিজের ছোট instruction-এর তালিকা, আর interpreter সেটা সঙ্গে সঙ্গে চালায়।
ধাপ 3: JIT ব্যস্ত code compile করে
Bytecode একটা একটা instruction করে চালালে শুরুটা দ্রুত হয়, কিন্তু চলাটা সবচেয়ে দ্রুত হয় না। Processor তার নিজের machine code অনেক দ্রুত চালায়। তাই V8 খেয়াল রাখে, কোন function বারবার চলছে। কোনোটা "hot" হয়ে গেলে দ্বিতীয় একটা অংশ ওটাকে machine code বানিয়ে দেয়। এই অংশটা TurboFan নামের optimising compiler।
JIT মানে এটাই: just-in-time compilation। Program চলার সময়ই engine code compile করে, আর শুধু সেই code, যেটার পেছনে খাটুনিটা পোষায়। Ignition আর TurboFan-এর মাঝে V8-এর আরো দুইটা মাঝারি স্তর আছে, Sparkplug আর Maglev। অন্য engine-গুলোতেও এমন স্তর আছে, অন্য নামে।
দ্রুত চলার জন্য TurboFan কিছু অনুমান করে নেয়। price আর count সব সময় সংখ্যা হলে সে সংখ্যার জন্য machine code বানায়। হঠাৎ একটা string চলে এলে engine ওই machine code ফেলে দিয়ে bytecode-এ ফিরে যায়। তোমার program তবুও ঠিক উত্তর দেয়; শুধু কিছুক্ষণ ধীরে চলে।
তাই JIT হলো এমন একটা compiler, যেটা run চলার সময় কাজ করে, আর শুধু বারবার চলা code-এর উপর।
Interpreted নাকি compiled? দুইটাই, আর দুইটাই আধা ঠিক
কোথাও পড়বে "JavaScript একটা interpreted language", আবার কোথাও "JavaScript compiled"। দুইটাই আধা সত্য। প্রতিটা লাইন প্রথমে bytecode হিসেবে interpret হয়। ব্যস্ত অংশগুলো পরে machine code-এ compile হয়। কোনো একটা নাম দিয়ে পুরো engine-টা বোঝানো যায় না।
কাজের পার্থক্যটা হলো, compile কখন হয়। একটা C compiler program চালানোর আগেই একবারে পুরোটা machine code বানিয়ে ফেলে। JavaScript engine সঙ্গে সঙ্গে চালানো শুরু করে, আর চলার মাঝে hot অংশগুলো compile করে। সেজন্যই একটা JavaScript program চোখের পলকে শুরু হয়, তারপর ধীরে ধীরে গতি বাড়ে।
C track করে থাকলে
C চলে উল্টো পথে। এর চারটা ধাপ (preprocessor, compiler, assembler, linker) সবই program চলার আগে হয়, আর শেষে machine code-এর একটা file তৈরি হয়। JavaScript এমন কোনো file রাখে না: program যতবার শুরু হয়, engine ততবার memory-তেই parse আর compile করে। C track-এর compile error আর JavaScript-এর জন্য Playground-এর "Compile error" একই গোত্রের: দুইটাই আসে চালানোর আগে লেখাটা পড়তে গিয়ে।
তাই JavaScript "শুধু interpreted"-ও না, "আগে থেকে compiled"-ও না; এটা compile হয় just in time।
দুইটা error, দুইটা ধাপ
প্রতিটা error message প্রথমে তার class-এর নাম বলে। আজ দুইটা class pipeline-এর উপর বসিয়ে দেখার মতো, কারণ ওরা আসে আলাদা ধাপ থেকে।
SyntaxError আসে parser থেকে। File-টা ব্যাকরণ মানেনি, তাই কিছুই চলে না, লাইন 1-ও না। এই হলো David-এর program, ভুলসহ:
console.log("Starting the report...");
const shop = "Bob's Books";
const sold = 12;
const price = 250;
const total = sold * price;
console.log(shop);
console.log("Books sold: " + sold);
console.log("Price each: " + price);
console.log("Total: " + total);
console.log("Done." +);
Playground run-টাকে "Compile error" হিসেবে দেখায়, আর তার "Compile output" tab-এ এটা ছাপে:
/box/main.js:10
console.log("Done." +);
^
SyntaxError: Unexpected token ')'
চালানোর আগে Playground parser দিয়ে তোমার file পরীক্ষা করে, সেজন্যই সে "compile" বলে। + কথা দিয়েছিল দ্বিতীয় একটা মান আসবে, এসেছে )। Playground-এর বাক্সের ভেতরে তোমার code-এর নাম হয় /box/main.js, আর :10 হলো লাইন নম্বর। Message-এর নিচে Node আরো দুইটা ভেতরের লাইন আর নিজের version নম্বর ছাপে; এখানে সেগুলো বাদ দেওয়া হয়েছে।
ReferenceError আসে code চলার সময়। ব্যাকরণ ঠিক ছিল, তাই program শুরু হয়েছে। তারপর সে এমন একটা নামে পৌঁছেছে, যেটার অস্তিত্বই নেই। ওই লাইনের আগের সবকিছু ততক্ষণে চলে গেছে, Example 2-এ যেমন দেখবে।
তাই SyntaxError মানে "কিছুই চলেনি", আর ReferenceError মানে "এই লাইন পর্যন্ত চলেছে"।
ছবি 1-এর লাইনটা, একটা ছোট function-এর ভেতরে, একবার call করা।
function total(price, count) {
return price * count;
}
console.log(total(250, 3));
750
একবার call হলে total কখনো hot হয় না, তাই bytecode হয়েই থাকে। তাতে সমস্যা নেই: তিনটা instruction চলতে এক সেকেন্ডের দশ লাখ ভাগের এক ভাগেরও অনেক কম সময় লাগে।
লাইন 3-এ একটা টাইপের ভুল: total-এর বদলে totl।
console.log("Line 1 runs.");
const total = 5;
console.log(totl);
console.log("Line 4 never runs.");
Line 1 runs.
ওই লাইনের পরে Playground একটা "Runtime error" দেখায়, এই message সহ:
/box/main.js:3
console.log(totl);
^
ReferenceError: totl is not defined
লাইন 1 ছেপেছে, কারণ parser খুশি ছিল আর program শুরু হয়েছিল। লাইন 3 চলার সময় fail করেছে। লাইন 4 কখনো চলেনি। Message-এর নিচে Node ভেতরের কিছু লাইনের একটা তালিকাও ছাপে, যাকে বলে stack trace; ওটা পড়তে শিখবে Module 10-এ।
Compiler-এ চালাওএই program খুব ছোট একটা function-কে 100,000 বার call করে, পরপর পাঁচ round, আর প্রতিটা round-এ কত সময় লাগল মাপে। এখনই প্রতিটা লাইন বুঝতে হবে না; loop হলো Module 4-এর বিষয়।
function add(a, b) { return a + b; }
for (let round = 1; round <= 5; round++) {
const start = process.hrtime.bigint();
let sum = 0;
for (let i = 0; i < 100000; i++) sum = add(sum, i);
const micros = Number(process.hrtime.bigint() - start) / 1000;
console.log("Round " + round + ": " + micros.toFixed(0) + " microseconds");
}
Round 1: 5415 microseconds
Round 2: 4376 microseconds
Round 3: 4169 microseconds
Round 4: 492 microseconds
Round 5: 147 microseconds
Playground-এ (Node.js 22) একবার চালানো, 2026 সালের 7 অক্টোবর। Round 1 থেকে round 5-এর মধ্যে একই কাজ প্রায় 37 গুণ দ্রুত হয়েছে, কারণ engine hot code-টাকে machine code-এ compile করে নিয়েছে। প্রতিবার চালালে তোমার সংখ্যা আলাদা আসবে, কিন্তু সময় কমে যাওয়াটা থাকবেই।
Compiler-এ চালাওএটা কোথায় কাজে লাগছে
- Chrome, Edge আর Node.js তিনটাই V8 চালায়, তাই এই lesson-এর bytecode browser tab-এ আর server-এ একই।
- Firefox চালায় SpiderMonkey। ওটার নিজের interpreter আর নিজের JIT স্তর আছে, নামগুলো আলাদা, কিন্তু নিয়মের বই একই।
- Safari আর Bun runtime চালায় JavaScriptCore, Apple-এর engine। এটাও interpreter দিয়ে শুরু করে, আর hot code compile করে।
- Progsity Playground চালায় Node.js 22, যার ভেতরে আছে V8 12.4। এটা আগে তোমার file parse করে, আর কিছু চলার আগেই SyntaxError-কে "Compile error" হিসেবে দেখায়।
যে ভুলগুলো সবাই করে
১. যে লাইনগুলো "চলেছে", তার মধ্যে bug খোঁজা।
David লাইন 1 দেখে ভাবছিল, ওটা ছাপল না কেন। SyntaxError-এর পরে কোনো লাইনই চলে না। Message যে লাইনের নাম বলছে, সোজা সেখানে যাও, আর যে token-এর দিকে দেখাচ্ছে সেটা পড়ো।
২. একটা বড় হাতের অক্ষর, আর নতুন একটা নাম।
const total = 750;
console.log(Total);
Node.js 22 বলে ReferenceError: Total is not defined। JavaScript-এর কাছে Total আর total দুইটা আলাদা নাম, আর আছে শুধু একটা। নাম হুবহু copy করো, বড় হাতের অক্ষর সহ। এই ভুলটা সবচেয়ে বেশি হবে কোনো কিছুর নাম এক জায়গায় বদলে আরেক জায়গায় বদলাতে ভুলে গেলে।
৩. বাদ পড়া bracket, যেটা ধরা পড়ে এক token পরে।
function total(price, count {
return price * count;
}
Playground একটা "Compile error" দেখায়, সঙ্গে SyntaxError: Unexpected token '{'। Parser বাদ পড়া )-টা টের পেয়েছে শুধু {-এ পৌঁছে। Message যদি এমন token দেখায় যেটা দেখতে ঠিকঠাক, তাহলে দেখো ঠিক তার আগে কী থাকার কথা ছিল।
৪. "Interpreted" বলে JavaScript ধীর, এটা বিশ্বাস করা।
Example 3 দেখায়, compile হওয়ার পর hot code প্রায় 37 গুণ দ্রুত চলে। বেশিরভাগ program-এর জন্য engine এতটাই দ্রুত যে ভাষার চেয়ে তোমার algorithm অনেক বেশি জরুরি। Module 17 এটা ঠিকমতো মেপে দেখায়।
Example 1 খোলো, আর call-টা বদলে total(120, 4) করো। চালাও।
নিজে যাচাই করো। এটা ছাপবে 480। তারপর * বদলে + করো, আবার চালাও: এবার ছাপবে 124।
Example 1-কে ইচ্ছা করে দুইবার ভাঙো, আর প্রতিবার Playground কী বলে লিখে রাখো। দুইবারের মাঝে ওটা আবার ঠিক করে নিও।
দুইটা ভাঙা। প্রথমবার, লাইন 1-এ count-এর পরের ) মুছে দাও। দ্বিতীয়বার, লাইন 2-এর count বদলে cont করো।
নিয়ম। প্রতিটা ভাঙার জন্য লেখো error-এর class, সে কোন লাইন নম্বরের কথা বলছে, আর Playground "Compile error" বলছে নাকি "Runtime error"।
নিজে যাচাই করো। একটা ভাঙা SyntaxError, আর তখন কিছুই ছাপে না। অন্যটা ReferenceError, যেটা লাইন 2-এর নাম বলে। দুইবারই একই class-এর error পেলে "দুইটা error, দুইটা ধাপ" অংশটা আবার পড়ো।
নিজের চোখে bytecode দেখো। এর জন্য নিজের মেশিনে Node.js বসানো লাগবে, কীভাবে বসাবে সেটা lesson 05 দেখাবে। Example 1 total.js নামে save করো, আর terminal-এ এটা চালাও:
node --print-bytecode --print-bytecode-filter=total total.js
নিয়ম। ছাপা লেখার মধ্যে এই lesson-এর তিনটা instruction খুঁজে বের করো। তারপর file-এ * বদলে + করো, command-টা আবার চালাও, আর বলো কোন instruction বদলেছে।
নিজে যাচাই করো। ঠিক একটা instruction বদলায়, আর তার নতুন নামই বলে দেয় সে কী করে। Playground এই command চালাতে পারবে না, কারণ ওটা কোনো flag নেয় না।
যে প্রশ্নগুলো সবার মনে আসে
JavaScript লিখতে কি bytecode বোঝা লাগবে?
না। তুমি কখনো এটা লিখবে না। তবে এটা যে আছে, সেটা জানলে রোজকার দুইটা ব্যাপার পরিষ্কার হয়: error কেন class ধরে আসে, আর চলতে চলতে code কেন দ্রুত হয়।
JavaScript compile না হলে Playground "Compile error" বলে কেন?
তোমার file চালানোর আগে Playground Node-কে বলে ওটা parse করতে, ঠিক ধাপ 1-এর মতো। Parser রাজি না হলে কিছুই চলে না, তাই ফলটা compile error হিসেবে দেখানো হয়। কথা বলছে parser, C-র মতো কোনো compiler না।
কোন engine সবচেয়ে দ্রুত?
এটা বছর বছর বদলায়, কারণ প্রতিটা team নিজেরটা উন্নত করতে থাকে। তিনটাই এত দ্রুত যে তোমার page বা server কত দ্রুত লাগবে, সেটা প্রায় কখনো engine ঠিক করে না।
JIT কি প্রতিটা program দ্রুত করে?
শুধু যে অংশগুলো অনেকবার চলে। ছোট program interpreter-এ থাকতে থাকতেই শেষ হয়ে যায়। তাতে সমস্যা নেই, কারণ ওটা এমনিতেই দ্রুত ছিল।
JIT-এর অনুমান কি ভুল উত্তর দিতে পারে?
না। অনুমান ভুল প্রমাণ হলে engine machine code ফেলে দিয়ে bytecode-এ ফিরে যায়। উত্তর ঠিকই থাকে; শুধু কিছুক্ষণের জন্য গতি বদলায়।
মূল কথা
- প্রায় সব browser আর server-এর JavaScript চালায় তিনটা engine: V8 (Chrome, Node.js), SpiderMonkey (Firefox), JavaScriptCore (Safari)।
- কোনো লাইন চলার আগেই parser পুরো file পড়ে একটা parse tree বানায়।
- Ignition tree থেকে bytecode বানিয়ে চালায়; TurboFan hot code-কে machine code-এ compile করে, just in time।
- "Interpreted" আর "compiled" দুইটাই আধা ঠিক: JavaScript আগে interpret হয়, আর যেখানে পোষায় সেখানে compile হয়।
- SyntaxError আসে parser থেকে, আর কিছুই চলে না; ReferenceError আসে run-এর মাঝে, আগের লাইনগুলো চলে যাওয়ার পরে।
এরপর তুমি নিজের প্রথম লাইনটা চালাবে, তিন জায়গায়, আর তিন জায়গা থেকেই একই উত্তর পাবে।
lesson ৪ শেষ
শেষ হলে চিহ্ন দিন, অগ্রগতি আপনার সাথে থাকবে।
পরেরটা: প্রথম run: console, Node, আর Playground