JavaScript চললে আসলে কী ঘটে? সোর্স কোড থেকে এক্সিকিউশন পর্যন্ত
আপনি লিখলেন:
const price = 100;
const taxRate = 0.15;
function calculateTotal(price) {
return price + price * taxRate;
}
console.log(calculateTotal(price));
তারপর code-টি চালালেন।
এক মুহূর্ত পর:
115
দেখা গেল।
বাইরে থেকে মনে হয় JavaScript যেন file-টি উপর থেকে নিচ পর্যন্ত পড়ে এবং প্রতিটি line সরাসরি execute করে।
JavaScript শেখার শুরুতে এই mental model বেশ কার্যকর।
কিন্তু এটি পুরো সত্য নয়।
আপনার code 115 ফলাফল দেখানোর আগেই একটি আধুনিক JavaScript engine-কে source code scan করতে, grammar parse করতে, internal representation তৈরি করতে, execution environment প্রস্তুত করতে, bytecode তৈরি করতে, runtime feedback সংগ্রহ করতে, বারবার চলা code machine code-এ compile করতে, সেটিকে optimize করতে এবং প্রয়োজনে সেই optimization বাতিল করতেও হতে পারে।
তাই JavaScript execution কেবল:
Source Code
↓
Execute
এতটুকু নয়।
আরও কার্যকর একটি mental model হলো:
Source Code
↓
Scanning / Parsing
↓
Internal Representation
↓
Execution Environment
↓
Bytecode / Machine Code
↓
Execution
↓
Runtime Feedback
↓
Optimization
এটিও একটি simplified model।
এবার শুরু থেকে পুরো process-টি অনুসরণ করা যাক।
সংক্ষিপ্ত উত্তর
JavaScript চলার আগে JavaScript engine-কে প্রথমে source code বুঝতে হয়। তারপরই সেটি code-এর অর্থ অনুযায়ী execution শুরু করতে পারে।
সাধারণভাবে process-টি এমন:
Runtime JavaScript source text গ্রহণ করে।
Engine source scan এবং parse করে।
Syntax এবং কিছু early error শনাক্ত হয়।
Program-এর internal representation তৈরি হয়।
ECMAScript semantics অনুযায়ী execution context এবং lexical environment প্রস্তুত হয়।
Program execute হতে শুরু করে।
আধুনিক engine প্রথমে bytecode-এর মতো intermediate representation execute করতে পারে।
বারবার execute হওয়া code machine code-এ compile হতে পারে।
Runtime information ব্যবহার করে hot code optimize করা হয়।
কোনো optimization-এর assumption ভুল হয়ে গেলে engine code deoptimize করতে পারে।
যেসব memory আর reachable নয়, garbage collector পরে সেগুলো reclaim করে।
এখানে একটি গুরুত্বপূর্ণ পার্থক্য আছে।
ECMAScript specification বলে JavaScript code-এর behavior কী হওয়া উচিত।
কিন্তু specification বলে না প্রতিটি engine-কে একই parser, bytecode format, compiler pipeline বা optimization strategy ব্যবহার করতেই হবে।
এই distinction পুরো article-এ গুরুত্বপূর্ণ।
JavaScript একটি Language, Runtime নয়
প্রথমেই একটি common misconception দূর করা দরকার:
JavaScript মানেই Chrome, Node.js, V8 অথবা browser event loop নয়।
JavaScript মূলত ECMAScript standard অনুযায়ী সংজ্ঞায়িত একটি programming language।
ECMAScript specification এমন code-এর behavior নির্ধারণ করে:
const x = 10;
function add(a, b) {
return a + b;
}
এটি define করে:
declaration কীভাবে কাজ করবে
function call কীভাবে হবে
scope কীভাবে কাজ করবে
object-এর behavior কী
execution context কীভাবে কাজ করবে
কিন্তু ECMAScript কোনো complete application environment নয়।
Network request, user interface, filesystem access কিংবা বিভিন্ন ধরনের I/O সাধারণত host environment provide করে।
তাই তিনটি আলাদা concept আছে:
JavaScript / ECMAScript
↓
Language semantics
JavaScript Engine
↓
Executes ECMAScript
Host Runtime
↓
Provides APIs and environment
Browser-এর ক্ষেত্রে:
Browser
├── JavaScript engine
├── DOM
├── timers
├── fetch
├── events
└── rendering engine
Node.js-এর ক্ষেত্রে:
Node.js
├── JavaScript engine
├── filesystem APIs
├── networking APIs
├── timers
└── runtime infrastructure
উদাহরণ হিসেবে, Chrome এবং Node.js উভয়েই V8 JavaScript engine ব্যবহার করে।
ধাপ ১: Engine Source Code গ্রহণ করে
সবকিছু শুরু হয় source text দিয়ে।
ধরা যাক:
function multiply(a, b) {
return a * b;
}
multiply(4, 5);
আমাদের কাছে এখানে পরিষ্কার:
একটি function আছে
দুটি parameter আছে
একটি return statement আছে
multiplication হচ্ছে
function call হচ্ছে
কিন্তু engine-এর কাছে শুরুতে এটি মূলত character-এর একটি sequence।
JavaScript grammar অনুযায়ী character-গুলোকে বুঝতে হবে।
CPU সরাসরি এই code execute করতে পারে না:
return a * b;
CPU JavaScript syntax বোঝে না।
CPU machine instructions বোঝে।
তাই JavaScript source এবং processor-এর মাঝখানে একটি বড় translation এবং execution pipeline দরকার।
ধাপ ২: Scanning Source Code-কে Meaningful অংশে ভাগ করে
ধরা যাক:
const answer = 40 + 2;
Conceptually engine এটিকে এমন কিছু অংশে ভাঙতে পারে:
const
answer
=
40
+
2
;
এগুলোকে সাধারণভাবে token বলা হয়।
Scanner বা lexer এমন জিনিস শনাক্ত করে:
keyword
identifier
numeric literal
string literal
operator
punctuation
উদাহরণ:
const total = price + tax;
এখানে:
const → keyword
total → identifier
= → operator
price → identifier
+ → operator
tax → identifier
; → punctuation
কিন্তু শুধু token থাকলেই program-এর structure পুরোপুরি বোঝা যায় না।
সেজন্য দরকার parsing।
ধাপ ৩: Parsing Program-এর Structure বোঝে
আবার:
const answer = 40 + 2;
Engine-কে বুঝতে হবে এর অর্থ conceptually:
Variable Declaration
├── Identifier: answer
└── Initializer
└── Addition Expression
├── Number: 40
└── Number: 2
এই ধরনের structural representation-কে সাধারণত Abstract Syntax Tree, বা AST, বলা হয়।
Conceptually:
Program
└── VariableDeclaration
└── VariableDeclarator
├── Identifier
│ └── answer
└── BinaryExpression
├── 40
├── +
└── 2
প্রতিটি engine-এর exact internal representation আলাদা হতে পারে।
V8-এর ক্ষেত্রে parser একটি AST তৈরি করতে পারে, যেটি পরবর্তী bytecode generation pipeline-এ ব্যবহৃত হয়।
Syntax Error এখানেই Program থামিয়ে দিতে পারে
ধরুন:
const user = ;
=-এর পরে কোনো valid expression নেই।
Source JavaScript grammar follow করছে না।
তাই engine program-এর valid structure তৈরি করতে পারবে না।
ফলাফল:
SyntaxError
এখানে একটি গুরুত্বপূর্ণ পার্থক্য:
Syntax problem
→ JavaScript program-টির structure সঠিকভাবে তৈরি করতে পারছে না
Runtime problem
→ JavaScript program বুঝেছে,
কিন্তু execution-এর সময় কিছু ব্যর্থ হয়েছে
উদাহরণ:
console.log(user.name);
এটি syntactically valid।
কিন্তু যদি:
user === undefined
তাহলে runtime-এ error হতে পারে।
Parser জানে expression valid।
কিন্তু runtime value কী হবে, সেটা parsing-এর সময় সবসময় জানা যায় না।
আধুনিক Engine সবকিছু সঙ্গে সঙ্গে পুরোপুরি Parse নাও করতে পারে
Parsing-এরও cost আছে।
ধরা যাক কোনো বড় web application-এ কয়েক megabyte JavaScript download হলো।
এর মধ্যে অনেক function হয়তো user-এর সেই session-এ কখনোই run করবে না।
সব function-এর জন্য শুরুতেই:
full AST
bytecode
compilation
করলে unnecessary work হতে পারে।
তাই modern engine lazy parsing বা preparsing-এর মতো technique ব্যবহার করতে পারে।
উদাহরণ:
function rarelyUsedFeature() {
// 500 lines of code
}
যদি function-টি কখনো call-ই না হয়, engine কিছু expensive processing পরে করার জন্য postpone করতে পারে।
এখান থেকে runtime engineering-এর একটি গুরুত্বপূর্ণ principle পাওয়া যায়:
কোনো কাজ ভবিষ্যতে লাগতে পারে বলে এখনই expensive কাজটি করে ফেলতে হবে—এমন নয়।
ধাপ ৪: JavaScript Execution Environment প্রস্তুত করে
Parsing আমাদের code-এর structure বোঝায়।
কিন্তু এটি এখনও explain করে না কেন:
console.log(name);
var name = "Geljek";
এবং:
console.log(name);
let name = "Geljek";
একইভাবে behave করে না।
এটি বুঝতে আমাদের compiler implementation থেকে সাময়িকভাবে ECMAScript execution model-এর দিকে যেতে হবে।
Specification execution context concept ব্যবহার করে।
একটি running execution context হলো বর্তমানে কোন code কোন environment-এর মধ্যে execute হচ্ছে তার logical representation।
Simplified mental model:
Execution Context
├── current code
├── lexical environment
├── variable environment
├── function information
└── other execution state
এটি কোনো engine-এর actual memory layout নয়।
এটি language semantics বোঝার mental model।
Global Execution Context
ধরুন:
const site = "Geljek";
function greet() {
return `Welcome to ${site}`;
}
greet();
Global-level code এমন binding তৈরি করে:
site
greet
তারপর greet() চললে function-এর জন্য নতুন execution context active হয়।
Conceptually:
Global Context
↓
greet() Context
greet() শেষ হলে:
greet() Context removed
↓
Global Context continues
Function Call নতুন Execution Context তৈরি করে
ধরা যাক:
function first() {
second();
}
function second() {
third();
}
function third() {
console.log("Done");
}
first();
শুরুতে:
Global
তারপর:
Global
first()
তারপর:
Global
first()
second()
তারপর:
Global
first()
second()
third()
সবশেষে enter করা execution context আগে execute হয়।
third() return করলে:
Global
first()
second()
তারপর:
Global
first()
শেষে:
Global
এই concept আমাদের নিয়ে আসে JavaScript-এর বহুল পরিচিত একটি বিষয়ের কাছে।
Call Stack
Call stack active function calls track করার একটি গুরুত্বপূর্ণ mental model।
উদাহরণ:
function calculate() {
return multiply(5, 10);
}
function multiply(a, b) {
return a * b;
}
calculate();
সবচেয়ে গভীর অবস্থায়:
┌──────────────────┐
│ multiply(5, 10) │
├──────────────────┤
│ calculate() │
├──────────────────┤
│ global code │
└──────────────────┘
multiply() return করলে:
┌──────────────────┐
│ calculate() │
├──────────────────┤
│ global code │
└──────────────────┘
তারপর calculate()-ও return করে।
তাহলে “Hoisting” আসলে কী?
JavaScript শেখার সময় প্রায়ই বলা হয়:
JavaScript declaration-গুলোকে উপরে তুলে দেয়।
Beginner level-এ এটি useful shortcut।
কিন্তু engine বাস্তবে source code rewrite করে না।
অর্থাৎ এই code:
console.log(name);
var name = "Abdullah";
সত্যিকার অর্থে engine এভাবে rewrite করে না:
var name;
console.log(name);
name = "Abdullah";
বরং statement execution-এর আগে declaration processing-এর সময় binding প্রস্তুত করা হয়।
Declaration-এর ধরন অনুযায়ী behavior আলাদা।
উদাহরণ:
console.log(name);
var name = "Geljek";
Output:
undefined
কিন্তু:
console.log(name);
let name = "Geljek";
error দেয়।
let এবং const binding environment setup-এর সময় তৈরি হয়, কিন্তু declaration evaluate হওয়ার আগে normal access করা যায় না।
এই interval-কে আমরা সাধারণত বলি:
Temporal Dead Zone (TDZ)
তাই:
“JavaScript variable-কে উপরে সরিয়ে দেয়”
এর চেয়ে ভালো mental model হলো:
JavaScript execution শুরু করার আগে binding তৈরি করে, কিন্তু declaration type অনুযায়ী initialization rules আলাদা।
ধাপ ৫: Engine-এর Executable Representation দরকার
এখন পর্যন্ত:
Source
↓
Tokens
↓
Parsed structure / AST
কিন্তু CPU এখনও AST execute করতে পারে না।
তাই modern engine source code-কে executable internal form-এ রূপান্তর করে।
এখানেই পুরোনো প্রশ্নটি আসে:
JavaScript কি interpreted নাকি compiled?
Modern JavaScript-এর জন্য এই binary question খুব useful নয়।
কারণ modern engines সাধারণত interpretation এবং compilation—দুটিই ব্যবহার করে।
Exact architecture engine অনুযায়ী আলাদা এবং সময়ের সঙ্গে evolve করে।
এখন V8-কে example হিসেবে দেখি।
V8-এর Execution Pipeline
Modern V8 একাধিক execution tier ব্যবহার করে।
Simplified model:
JavaScript Source
↓
Parser
↓
AST
↓
Ignition Bytecode
↓
Execution + Runtime Feedback
↓
Faster compilation tiers
↓
Optimized Machine Code
V8-এর Ignition component JavaScript থেকে bytecode তৈরি করে।
তারপর Ignition interpreter সেই bytecode execute করতে পারে।
তবে modern V8 শুধু:
Ignition → TurboFan
এই পুরোনো two-stage model-এ সীমাবদ্ধ নয়।
বর্তমান V8 execution pipeline-এ বিভিন্ন compilation tier আছে, যেমন:
Ignition
Sparkplug
Maglev
TurboFan
Simplified diagram:
JavaScript
↓
Parser
↓
Ignition Bytecode
↓
Ignition
Interpreter
│
Runtime Feedback
│
┌──────────┼──────────┐
↓ ↓ ↓
Sparkplug Maglev TurboFan
baseline optimizing high-tier
compiler compiler optimizer
এটিও simplified, কিন্তু “JavaScript শুধু interpret হয়” ধারণার চেয়ে অনেক বেশি accurate।
Bytecode কী?
CPU architecture অনুযায়ী machine code ভিন্ন হয়।
যেমন:
x86-64
ARM64
এদের instruction set আলাদা।
Bytecode হলো engine-এর ব্যবহৃত একটি intermediate representation।
ধরা যাক:
function add(a, b) {
return a + b;
}
Conceptually bytecode এমন instruction represent করতে পারে:
Load argument a
Load argument b
Add
Return
এটি actual V8 bytecode নয়।
শুধু concept বোঝানোর জন্য।
মূল relationship:
JavaScript Source
↓
Intermediate Instructions
↓
Engine Executes Them
সবকিছু শুরুতেই Highly Optimized Machine Code-এ Compile করা হয় না কেন?
কারণ optimization নিজেও expensive।
ধরুন:
function add(a, b) {
return a + b;
}
এই function চলতে পারে:
1 time
অথবা:
1,000,000,000 times
একবার চলবে এমন function aggressively optimize করতে যদি বেশি CPU time লাগে, তাহলে optimization-এর cost তার benefit-এর চেয়েও বেশি হতে পারে।
এখানে fundamental tradeoff:
Compile Quickly
vs
Execute Quickly
Fast compiler দ্রুত execution শুরু করতে পারে।
Sophisticated optimizing compiler আরও দ্রুত machine code তৈরি করতে পারে, কিন্তু analysis এবং compilation-এ বেশি সময় লাগে।
Tiered execution এই দুইয়ের মধ্যে balance আনার চেষ্টা করে।
প্রথমে:
cheap execution
তারপর গুরুত্বপূর্ণ code-এর জন্য:
better optimized execution
Hot Code
যে code খুব ঘন ঘন execute হয় তাকে সাধারণত hot code বলা হয়।
উদাহরণ:
function square(number) {
return number * number;
}
for (let i = 0; i < 10_000_000; i++) {
square(i);
}
এখানে square() লক্ষ লক্ষবার run করছে।
তাই function-টি optimize করা লাভজনক হতে পারে।
Engine execution চলাকালে runtime information collect করতে পারে।
উদাহরণ হিসেবে engine observe করতে পারে:
number সবসময় numeric value পাচ্ছে
এই information ব্যবহার করে compiler আরও specialized এবং faster code তৈরি করতে পারে।
JavaScript-এর Dynamic Nature Optimization-কে Interesting করে তোলে
ধরা যাক:
function add(a, b) {
return a + b;
}
add(10, 20);
add(100, 200);
add(5, 7);
এখন পর্যন্ত:
number + number
তারপর:
add("Gel", "jek");
এখন:
string + string
JavaScript এটি allow করে।
Source code দেখে engine সবসময় নিশ্চিত হতে পারে না যে a এবং b সারাজীবন number থাকবে।
কিন্তু যদি runtime-এ বারবার দেখা যায়:
add(1, 2);
add(3, 4);
add(5, 6);
তাহলে optimizing compiler runtime behavior-এর ওপর ভিত্তি করে specialized machine code তৈরি করতে পারে।
Conceptually:
Observation:
a repeatedly looks like Number
b repeatedly looks like Number
Optimization:
Generate a fast path for numbers
প্রতিবার সম্ভাব্য সব JavaScript type handle করার চেয়ে এই fast path অনেক দ্রুত হতে পারে।
Optimization অনেক সময় Speculative
Modern JavaScript engine সম্পর্কে সবচেয়ে গুরুত্বপূর্ণ concept-গুলোর একটি:
Optimized execution অনেক সময় speculation-এর ওপর নির্ভর করে।
Engine observe করতে পারে:
"This function seems to receive numbers."
তারপর সেই assumption ধরে optimized code generate করতে পারে।
কিন্তু JavaScript dynamic।
Assumption পরে ভুল হতে পারে।
উদাহরণ:
function double(value) {
return value + value;
}
for (let i = 0; i < 100000; i++) {
double(i);
}
double("Geljek");
দীর্ঘ সময়:
number → number
তারপর:
string → string
Engine-কে তখনও JavaScript-এর correct semantics বজায় রাখতে হবে।
Performance optimization কখনো language behavior বদলাতে পারে না।
Deoptimization
ধরুন optimized machine code তৈরি করা হয়েছে একটি assumption-এর ওপর।
পরে assumption আর valid থাকল না।
Engine তখন optimized path ছেড়ে safer execution mode-এ ফিরে যেতে পারে।
এটিকে বলা হয়:
Deoptimization
Conceptually:
General execution
↓
Observe behavior
↓
Optimize
↓
Assumption fails
↓
Deoptimize
↓
Return to safer execution
এ কারণেই:
“JavaScript dynamically typed, তাই optimize করা যায় না”
এই statement অতিরিক্ত simplified।
Dynamic behavior optimization কঠিন করে।
Impossible করে না।
Object-এর Shape-ও গুরুত্বপূর্ণ
ধরা যাক:
const user1 = {
name: "Amina",
age: 24,
};
const user2 = {
name: "Rahim",
age: 29,
};
দুটি object-এর structure একই:
name
age
এখন:
const user3 = {
username: "karim",
score: 100,
verified: true,
};
এটির structure আলাদা।
Modern engine object-এর shape সম্পর্কে information ব্যবহার করে optimization করতে পারে।
V8 runtime execution-এর সময় object structure সম্পর্কিত information track করতে পারে এবং সেগুলো optimization pipeline-এ ব্যবহার করতে পারে।
এর অর্থ JavaScript performance শুধু individual line-এর ওপর নির্ভর করে না।
Runtime behavior pattern-ও matter করতে পারে।
তবে এর মানে এই নয় যে application code-কে obscure engine trick-এর জন্য unreadable বানাতে হবে।
প্রথম priority হওয়া উচিত:
readable code
good algorithms
maintainable architecture
Engine internals জানা মূলত ভালো mental model তৈরির জন্য।
Function Call-এর সময় আসলে কী ঘটে?
এই program দেখি:
const multiplier = 2;
function multiply(value) {
const result = value * multiplier;
return result;
}
const answer = multiply(21);
console.log(answer);
Conceptually execution এমন হতে পারে।
১. Global environment প্রস্তুত হয়
Binding তৈরি হয়:
multiplier
multiply
answer
প্রতিটির declaration semantics অনুযায়ী।
২. Global statement execute হয়
const multiplier = 2;
evaluate হয়।
৩. multiply(21) পাওয়া যায়
নতুন function execution context তৈরি হয়।
Conceptually:
multiply Execution Context
├── value = 21
└── access to outer lexical environment
৪. Function body execute হয়
const result = value * multiplier;
Engine প্রথমে resolve করে:
value
function-এর local environment থেকে।
তারপর:
multiplier
outer lexical environment থেকে।
৫. Function return করে
return result;
ফলাফল:
42
Function execution context আর running context থাকে না।
৬. Global execution চলতে থাকে
answer = 42
তারপর:
console.log(answer);
execute হয়।
এখান থেকেই Closure-ও বোঝা যায়
উদাহরণ:
function createCounter() {
let count = 0;
return function increment() {
count++;
return count;
};
}
const counter = createCounter();
counter(); // 1
counter(); // 2
counter(); // 3
createCounter() ইতিমধ্যে return করেছে।
তারপরও inner function access করতে পারছে:
count
কেন?
কারণ JavaScript-এর lexical environment model অনুযায়ী function তার creation environment-এর binding access ধরে রাখতে পারে।
এই relationship-এর ওপরই closure concept দাঁড়িয়ে আছে।
Conceptually:
increment()
↓
its lexical environment
↓
createCounter environment
↓
count
Closure আলাদা কোনো magical feature নয়।
Lexical scope এবং environment resolution-এর natural consequence।
Event Loop এখানে কোথায়?
এখানে অনেক explanation ভুলভাবে engine এবং host runtime একসঙ্গে mix করে ফেলে।
JavaScript engine JavaScript execute করে।
Surrounding runtime asynchronous capability coordinate করে।
উদাহরণ:
console.log("A");
setTimeout(() => {
console.log("B");
}, 0);
console.log("C");
Output:
A
C
B
setTimeout()-এ JavaScript কেন থেমে থাকে না?
কারণ timer mechanism host runtime-এর capability।
Runtime callback-টিকে future execution-এর জন্য schedule করতে পারে, আর JavaScript meanwhile execution চালিয়ে যেতে পারে।
Simplified picture:
JavaScript Engine
↕
Host Runtime
├── Timers
├── Network
├── Events
└── Task Scheduling
JavaScript language নিজে কোনো complete I/O environment নয়।
এই distinction আরও গুরুত্বপূর্ণ হয় যখন JavaScript চলে:
Chrome
Node.js
Deno
Bun
embedded systems
other hosts
Language একই।
Host API এবং scheduling infrastructure আলাদা হতে পারে।
Promise-এর ক্ষেত্রে কী ঘটে?
উদাহরণ:
console.log("Start");
Promise.resolve().then(() => {
console.log("Promise");
});
console.log("End");
এখানে দুটো layer বুঝতে হবে।
Language-level
JavaScript Promise object এবং promise reaction-এর semantics define করে।
Runtime scheduling
Host execution environment এগুলোকে scheduling model-এর সঙ্গে integrate করে।
তাই asynchronous JavaScript শুধুমাত্র call stack দিয়ে explain করা যায় না।
Call stack বলে:
এখন কী execute হচ্ছে
কিন্তু async scheduling-এর জন্য জানতে হয়:
পরে কী execute হতে পারে
এটি আলাদা deep dive-এর বিষয়।
Memory কোথায় আসে?
JavaScript execute করতে memory প্রয়োজন।
উদাহরণ:
const user = {
name: "Amina",
articles: ["JavaScript", "Databases", "Networks"],
};
Engine-কে memory allocate করতে হয়:
value
object
array
function
internal runtime structure
Program চলতে চলতে আরও data তৈরি হয়:
const result = {
id: crypto.randomUUID(),
createdAt: new Date(),
};
কিছু object reachable থাকে।
কিছু object পরে unreachable হয়ে যায়।
JavaScript engine-এর garbage collector unreachable memory identify করে পরে reclaim করতে পারে।
উদাহরণ:
function work() {
const temporary = {
data: new Array(1000),
};
}
work();
Developer-কে manually:
free(temporary)
করতে হয় না।
Garbage collection নিজেই একটি বড় বিষয়।
এর মধ্যে আছে:
reachability
generations
marking
sweeping
compaction
write barriers
incremental collection
concurrent collection
এখন শুধু মনে রাখুন:
JavaScript execution শুধু CPU instruction চালানো নয়। Engine-কে একই সঙ্গে memory lifecycle-ও manage করতে হয়।
তাহলে JavaScript Interpreted নাকি Compiled?
Practical answer:
Modern JavaScript engine interpretation এবং compilation—দুটিই ব্যবহার করে।
শুধু:
interpreted language
বলা outdated।
আবার শুধু:
compiled language
বললেও গুরুত্বপূর্ণ detail হারিয়ে যায়।
V8-এর ক্ষেত্রে source:
parse
↓
Ignition bytecode
↓
interpretation
↓
runtime feedback
↓
additional compilation tiers
↓
optimized machine code
এর মধ্য দিয়ে যেতে পারে।
এটিকে broadly বলা যায়:
Tiered Execution Architecture
ECMAScript Specification-এ Ignition বা TurboFan নেই কেন?
কারণ এগুলো implementation detail।
Specification বলে observable behavior কী হতে হবে।
উদাহরণ:
let x = 5;
x++;
এর meaning কী হবে, সেটা specification define করে।
কিন্তু specification বলে না:
You must compile this using Ignition.
যদি বলত, তাহলে engine developer-রা নতুন implementation strategy ব্যবহার করতে পারত না।
তাই বিভিন্ন engine আলাদাভাবে implement করতে পারে:
parser
bytecode
intermediate representation
compiler
optimizer
garbage collector
machine code generation
তবু developer-এর JavaScript একই semantics follow করবে।
Specification বনাম Engine বনাম Runtime
এখন পুরো model-টি পরিষ্কারভাবে দেখা যাক।
ECMAScript Specification
Define করে:
JavaScript-এর অর্থ কী
যেমন:
syntax
variable
function
object
lexical environment
execution context
promise
language semantics
JavaScript Engine
এই semantics implement করে।
এর দায়িত্বের মধ্যে থাকতে পারে:
parsing
bytecode
compilation
optimization
garbage collection
machine execution
Host Runtime
JavaScript-এর চারপাশের environment দেয়।
যেমন:
timers
networking
DOM
filesystem
events
process APIs
একসঙ্গে:
┌──────────────────────────────────────────┐
│ Host Runtime │
│ │
│ APIs, timers, network, DOM/filesystem │
│ │
│ ┌──────────────────────────────────┐ │
│ │ JavaScript Engine │ │
│ │ │ │
│ │ Parser │ │
│ │ Execution engine │ │
│ │ Compiler │ │
│ │ Optimizer │ │
│ │ Garbage collector │ │
│ │ │ │
│ │ Implements ECMAScript semantics │ │
│ └──────────────────────────────────┘ │
└──────────────────────────────────────────┘
JavaScript developer-এর জন্য এটি সবচেয়ে গুরুত্বপূর্ণ mental model-গুলোর একটি।
পুরো Journey একসঙ্গে
আবার শুরুতে ফিরে আসি:
const price = 100;
const taxRate = 0.15;
function calculateTotal(price) {
return price + price * taxRate;
}
console.log(calculateTotal(price));
একটি deliberately simplified journey:
১. Source আসে
Characters
২. Scanner lexical element শনাক্ত করে
const
price
=
100
...
৩. Parser grammar analyze করে
Program
├── declarations
├── function
└── call expression
৪. Internal representation তৈরি হয়
V8-এর মতো engine-এ parsing AST তৈরি করতে পারে।
৫. Execution environment তৈরি হয়
Binding, lexical environment এবং execution context প্রস্তুত হয়।
৬. Executable representation তৈরি হয়
V8-এর ক্ষেত্রে:
AST
↓
Ignition bytecode
৭. Execution শুরু হয়
Interpreter bytecode execute করতে পারে।
৮. Function call হয়
Function enter এবং return করার সঙ্গে execution context পরিবর্তিত হয়।
৯. Runtime feedback সংগ্রহ হয়
Engine execution behavior observe করতে পারে।
১০. গুরুত্বপূর্ণ code faster tier-এ compile হতে পারে
Hot code আরও optimized machine code পেতে পারে।
১১. Optimized machine code execute হয়
শেষ পর্যন্ত CPU engine-generated machine instructions execute করে।
১২. Assumption ভুল হতে পারে
তখন:
deoptimization
ঘটতে পারে।
১৩. Memory manage হতে থাকে
Unreachable data পরে garbage collector reclaim করতে পারে।
এত কিছু ঘটল কারণ আমরা চেয়েছিলাম:
115
JavaScript-এর জন্য Better Mental Model
Beginner level-এ:
Read code
↓
Execute code
যথেষ্ট।
কিন্তু deeper understanding-এর জন্য:
Source
↓
Parse
↓
Represent
↓
Prepare execution state
↓
Execute
↓
Observe
↓
Compile
↓
Optimize
↓
Possibly deoptimize
Async operation যোগ হলে:
JavaScript Engine
↕
Host Runtime
↕
Task Scheduling
↕
External Systems
প্রতিটি deeper model আগের simplified model-এর limitation explain করে।
Software engineering-এ এটি খুব common pattern।
Abstraction দরকার।
কিন্তু কঠিন bug অনেক সময় abstraction-এর নিচে থাকে।
সাধারণ ভুল ধারণা
“JavaScript line by line execute করে।”
শুধু source-level beginner mental model হিসেবে কিছুটা useful।
বাস্তবে engine execution-এর আগে parsing, environment preparation এবং অন্যান্য processing করে।
“JavaScript একটি interpreted language।”
অসম্পূর্ণ।
Modern engines interpretation-এর পাশাপাশি multiple compilation tier ব্যবহার করতে পারে।
“Hoisting মানে declaration physically উপরে চলে যায়।”
না।
Binding declaration semantics অনুযায়ী execution-এর আগে establish হয়।
Source code physically move হয় না।
“Event loop JavaScript engine-এর অংশ।”
সঠিকভাবে বললে JavaScript engine এবং host runtime আলাদা concept।
Async scheduling host environment-এর সঙ্গে সম্পর্কিত।
“Optimized JavaScript একবার optimize হলে সবসময় optimized থাকে।”
না।
Speculative assumption invalid হলে deoptimization ঘটতে পারে।
“সব JavaScript engine V8-এর মতো কাজ করে।”
না।
V8 একটি implementation।
অন্য engine ভিন্ন internal architecture ব্যবহার করতে পারে।
Full-Stack Developer-এর এটি জানা কেন দরকার?
CRUD application বানানোর সময় প্রতিদিন TurboFan নিয়ে ভাবতে হবে না।
কিন্তু JavaScript execution model বুঝলে অনেক বিষয় পরিষ্কার হয়:
scope
closures
call stack
recursion
hoisting
temporal dead zone
asynchronous execution
runtime performance
memory
optimization
deoptimization
browser ও Node.js-এর পার্থক্য
সবচেয়ে বড় পরিবর্তন আসে আপনার প্রশ্ন করার ধরনে।
শুধু মুখস্থ না করে:
letএবংvarআলাদা।
আপনি প্রশ্ন করবেন:
তাদের binding কীভাবে create এবং initialize হয়?
শুধু মুখস্থ না করে:
JavaScript single-threaded।
আপনি প্রশ্ন করবেন:
কোন অংশ single-threaded? Host runtime কী করছে? Async operation কীভাবে schedule হচ্ছে?
শুধু বলবেন না:
V8 JavaScript fast করে।
বরং প্রশ্ন করবেন:
V8 কীভাবে runtime feedback সংগ্রহ করে? কোন compilation tier আছে? কোন tradeoff-এর কারণে এগুলো দরকার?
এই ধরনের প্রশ্ন framework knowledge থেকে engineering knowledge-এর দিকে নিয়ে যায়।
Final Mental Model
এই article থেকে একটি diagram মনে রাখতে চাইলে এটিই রাখুন:
JavaScript Source
│
▼
Scan and Parse
│
▼
Internal Representation
│
▼
Prepare Execution Contexts
│
▼
Executable Bytecode
│
▼
Execute
│
Runtime Feedback
│
┌────────────┴────────────┐
▼ ▼
Keep Executing Optimize
│
▼
Machine Instructions
│
Assumption changes?
│
Yes ▼
Deoptimize
Exact implementation engine অনুযায়ী বদলাতে পারে।
কিন্তু মূল lesson একই:
আপনি যে JavaScript source code লেখেন, সেটি execution-এর কেবল শুরু। আপনার code এবং processor-এর machine instructions-এর মাঝখানে একটি পূর্ণাঙ্গ language implementation কাজ করে—যেটি code parse করে, execution state manage করে, runtime behavior observe করে, compile করে, optimize করে, প্রয়োজনে deoptimize করে এবং memory manage করে।
এটি বুঝতে পারলে JavaScript আর magic-এর মতো লাগে না।
এটি একটি system হিসেবে দেখা শুরু হয়।
এরপর কী?
আমরা এখন বুঝলাম JavaScript কীভাবে execution পর্যন্ত পৌঁছায়।
কিন্তু একটি বড় প্রশ্ন ইচ্ছাকৃতভাবে এখনও বাদ রেখেছি:
console.log("A");
setTimeout(() => {
console.log("B");
}, 0);
Promise.resolve().then(() => {
console.log("C");
});
console.log("D");
এটি কেন শুধু উপর থেকে নিচে execute হয়ে:
A
B
C
D
দেয় না?
এর উত্তর JavaScript engine-এর বাইরের আরও কয়েকটি concept-এর সঙ্গে জড়িত:
Call Stack
Tasks
Microtasks
Promises
Host Runtime
Asynchronous Scheduling
এগুলোই আমাদের পরবর্তী article-এর বিষয়:
JavaScript Event Loop: Call Stack, Microtasks, Macrotasks এবং এর ভেতরের পুরো প্রক্রিয়া
তথ্যসূত্র
এই article-এর technical model প্রধানত ECMAScript specification এবং V8-এর official engineering documentation-এর ওপর ভিত্তি করে তৈরি।
বিশেষভাবে পড়তে পারেন:
ECMAScript Language Specification — JavaScript semantics, execution contexts এবং lexical environments
V8 Documentation — JavaScript engine architecture
V8 Ignition Interpreter — bytecode execution
V8 Sparkplug — baseline compilation
V8 Maglev — mid-tier optimizing compiler
V8 TurboFan — high-tier optimization
V8 Parsing and Preparser Documentation — parsing এবং lazy processing
JavaScript specification মূলত নির্ধারণ করে JavaScript কীভাবে behave করবে, আর V8 documentation দেখায় একটি বাস্তব modern engine সেই semantics বাস্তবে কীভাবে implement করতে পারে।
আলোচনা
সাইন ইন করে আলোচনায় যোগ দিন।