JavaScript Runtime Deep Guide
Mở đầu
Đã học JS basic syntax, nhưng có nghĩ:
- Code chạy ở đâu?
- Sao cùng code chạy browser + Node.js behavior khác nhau?
- Sao code đôi khi "kẹt", đôi khi "parallel"?
Bài này dẫn sâu vào runtime env của JS: event loop, call stack, memory management. Đọc xong, hiểu sao code execute theo order nào, define bug async nhanh, optimize perf + tránh memory leak.
Bạn sẽ học:
| Chương | Nội dung |
|---|---|
| 1 | Runtime overview |
| 2 | Browser runtime |
| 3 | Node.js runtime |
| 4 | Event loop sâu |
| 5 | Call stack + memory |
| 6 | Thực chiến |
1. Runtime overview
Core
"Runtime" là gì? JS là ngôn ngữ, sao cùng code env khác behavior khác?
1.1 Runtime là gì
Runtime = JS engine + API env cung cấp
JS = "ngôn ngữ", runtime = "OS" — quyết code làm gì được, không được.
┌─────────────────────────────────────┐
│ JavaScript code │
├─────────────────────────────────────┤
│ JS engine (V8) │ ← Parse + execute code
├─────────────────────────────────────┤
│ Runtime env (Browser/Node.js) │ ← Cấp khả năng thêm
└─────────────────────────────────────┘Ẩn dụ: JS là "tiếng phổ thông", runtime là "thành phố"
- Syntax JS (phổ thông) ở đâu cũng vậy
- Nhưng thành phố khác cấp tiện ích khác:
- Browser = DOM, window, fetch (như TP có shopping mall, library)
- Node.js = fs, http, path (như TP có factory, highway)
1.2 2 mainstream runtimes
| Feature | Browser | Node.js |
|---|---|---|
| Use chính | Web interaction, UI | Server, CLI tool |
| Global object | window | global |
| DOM API | ||
| File system | (hạn chế) | Full |
| Module | ES Modules | CommonJS + ES Modules |
| Timer | setTimeout, setInterval | Y vậy |
| Network | fetch, XMLHttpRequest | http, https module |
👇 So sánh env browser + Node.js:
运行时环境对比
浏览器环境
- ✅ 有 DOM 和 BOM API,可以操作网页
- ✅ 有 Web Storage (localStorage, sessionStorage)
- ✅ 有 fetch 和 XMLHttpRequest 进行网络请求
- ❌ 没有文件系统访问权限
- ❌ 不能直接创建 HTTP 服务器
代码演示:不同环境的差异
核心区别:
浏览器运行时专注于用户界面和网页交互,提供 DOM、BOM、fetch 等前端专用 API。
Node.js 运行时专注于服务器端开发,提供文件系统、HTTP 服务器、进程管理等后端专用 API。
同样的 JavaScript 语法,但能用的 API 完全不同——这就是"环境判断"的重要性。
Insight
Runtime quyết API bạn dùng được. DOM API trong browser không dùng được trong Node.js; file API trong Node.js không dùng được trong browser. Đó là sao 1 số code cần "env check".
2. Browser runtime
2.1 Cấu tạo
┌─────────────────────────────────────────────┐
│ JS engine (V8/SpiderMonkey) │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ Web APIs │
│ DOM (op web) | BOM (op browser) | Network │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ Event Loop │
│ Coordinate execute + event + task │
└─────────────────────────────────────────────┘2.2 3 loại Web APIs
1. DOM API - op web content
const title = document.querySelector('h1')
title.textContent = 'Title mới'
title.style.color = 'red'2. BOM API - op browser
window.location.href = 'https://example.com'
localStorage.setItem('key', 'value')
history.back()3. Network API
fetch('/api/data')
.then(response => response.json())
.then(data => console.log(data))2.3 Event-driven
Browser runtime mạnh nhất là "event-driven" — code không chạy liên tục, đợi user action.
button.addEventListener('click', () => {
console.log('Button clicked')
})| Event | Trigger | Use |
|---|---|---|
click | Mouse click | Button |
input | Input change | Search realtime |
scroll | Page scroll | Lazy load |
load | Resource loaded | Init data |
error | Lỗi | Error handling |
3. Node.js runtime
3.1 Cấu tạo
┌─────────────────────────────────────────────┐
│ JS engine (V8) │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ Node.js built-in modules │
│ fs (file) | http (server) | path │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ libuv event loop library │
│ Cross-platform async I/O │
└─────────────────────────────────────────────┘3.2 Node.js features
1. File system
const fs = require('fs')
fs.readFile('./data.txt', 'utf8', (err, data) => {
if (err) throw err
console.log(data)
})2. HTTP server
const http = require('http')
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/html' })
res.end('<h1>Hello World</h1>')
})
server.listen(3000)3. Module system
// CommonJS (Node.js default)
const fs = require('fs')
module.exports = { myFunction }
// ES Modules (modern)
import fs from 'fs'
export { myFunction }3.3 Browser vs Node.js
| Feature | Browser | Node.js |
|---|---|---|
| Entry | HTML file | JS file |
| Global | window, document | global, process |
| Module load | <script> | require() / import |
| Security | Sandbox, hạn chế | Access system resource |
| Use | UI | Backend, tool |
4. Event loop sâu
Core
JS là single-thread, sao "non-blocking"?
4.1 Event loop là gì
Event loop = "task scheduler" của JS
JS single-thread, 1 lúc 1 việc. Event loop làm cảm giác "song song".
Core:
- Execute sync code (call stack)
- Xử async task (task queue)
- Chờ task mới (loop)
Call stack Task queue
┌─────────┐ ┌──────────┐
│ Task 1 │ │ Macro 1 │
│ Task 2 │ ←───────────│ Macro 2 │
│ Task 3 │ Xong 1, lấy │ Macro 3 │
└─────────┘ tiếp └──────────┘
↓ ↑
└───────────────────────┘
Event loop check liên tục4.2 Macrotask vs Microtask
Đây là concept hay nhầm nhất!
Macrotask:
setTimeout,setInterval- I/O
- UI render
Microtask:
Promise.thenMutationObserverqueueMicrotask
Order: sync code → microtask → macrotask
👇 Quan sát order:
任务队列:宏任务 vs 微任务
代码示例
调用栈 (正在执行)
微任务队列 Microtask
宏任务队列 Macrotask
输出日志 (执行顺序)
执行顺序规则
核心要点: 微任务优先级高于宏任务。每次执行完一个宏任务后,都会检查并执行所有微任务,然后再执行下一个宏任务。
4.3 Câu phỏng vấn kinh điển
console.log('1')
setTimeout(() => console.log('2'), 0)
Promise.resolve().then(() => console.log('3'))
console.log('4')
// Output: 1, 4, 3, 2Lý do:
- Sync code: 1, 4
- Microtask: Promise.then → 3
- Macrotask: setTimeout → 2
Practical
- Muốn execute sớm → microtask (
Promise.then) - Muốn delay → macrotask (
setTimeout) - Tránh mix nhiều async → "callback hell"
5. Call stack + memory
5.1 Call stack: dấu chân function
function a() { b() }
function b() { c() }
function c() { console.log('Done') }
a()Stack change:
Step 1: call a() Step 2: a→b Step 3: a→b→c Step 4: c done
┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐
│ a │ │ b │ │ c │ │ b │
└─────┘ │ a │ │ b │ │ a │
└─────┘ │ a │ └─────┘
└─────┘调用栈:函数执行的足迹
代码
调用栈
当前状态:
调用 main()
输出
调用栈工作原理:
- 每次调用函数,就会在栈上"压入"一个新的"栈帧"
- 栈帧记录了函数的执行状态、局部变量等信息
- 函数执行完毕,栈帧就会从栈上"弹出"
- 栈是"后进先出"(LIFO)的数据结构
- 如果递归太深,会导致"栈溢出"错误
调用栈就像一摞盘子:最后放上去的盘子最先被取走。每个函数就是一个盘子,执行完就取走,然后继续执行下面的函数。
5.2 Memory management
JS có auto garbage collection — không cần free memory thủ công.
Mark-Sweep algorithm:
- Mark: từ "root" tìm mọi variable accessible
- Sweep: variable không mark = "rác" → recycle
let obj1 = { name: 'Object 1' }
let obj2 = { name: 'Object 2' }
obj1 = null // Object 1 ban đầu được recycle
console.log(obj2.name) // Object 2 vẫn dùng, không recycle垃圾回收机制
对象引用关系
标记-清除算法 (Mark-and-Sweep)
从根对象(Root)开始,遍历所有可达对象,标记为"活动对象"
遍历整个堆内存,回收所有未被标记的对象
清除所有标记位,为下一次垃圾回收做准备
核心要点
- 根对象(Root): 全局变量、栈上的变量等,总是被认为是可达的
- 可达对象: 从根对象出发,通过引用链能访问到的对象
- 垃圾对象: 无法从根对象访问到的对象,会被回收
- 循环引用: 如果两个对象互相引用但都不可达,仍会被回收
实际应用技巧
对象不再使用时,将其设为 null
使用 const/let 代替 var
组件销毁时移除所有监听器
用 DevTools Memory 面板监控
5.3 Memory leak
Memory leak = memory đáng release nhưng không, tích lũy
1. Global variable quá nhiều
// ❌ Global không recycle
globalCache = []
function addItem(item) { globalCache.push(item) }2. Event listener không remove
// ❌
button.addEventListener('click', handleClick)
// ✅
button.removeEventListener('click', handleClick)3. Closure reference object lớn
// ❌ Closure reference forever
function createHandler() {
const bigData = new Array(1000000).fill('data')
return function() { console.log('Processing') }
}
const handler = createHandler() // bigData luôn trong memory内存泄漏演示
全局变量泄漏
问题:全局变量不会被垃圾回收,会一直占用内存
示例:不断往全局数组添加数据,从不清理
❌ 错误做法
// 全局变量不会被回收
globalCache = []
function addItem() {
globalCache.push(largeData)
}如何避免内存泄漏
- 避免全局变量: 使用 const/let 代替 var,尽量使用局部变量
- 及时清理监听器: 组件销毁时移除所有事件监听
- 释放闭包引用: 不需要时将闭包变量设为 null
- 使用 WeakMap/WeakSet: 自动清理不再被引用的对象
- 定期检查: 用 DevTools Memory 面板检查内存泄漏
Practical
- Check định kỳ: DevTools → Memory → Heap Snapshot
- Tránh global: dùng
const,let, khôngvar - Clean kịp thời: event listener + timer xong remove
- Weak reference:
WeakMap,WeakSetcho object reference
6. Thực chiến
6.1 Performance
1. Giảm reflow/repaint
// ❌ Loop trigger reflow mỗi lần
for (let i = 0; i < 1000; i++) {
element.style.top = i + 'px'
}
// ✅ Batch
element.style.transform = `translateY(${position}px)`2. Event delegation
// ❌ Add listener mỗi button
buttons.forEach(btn => btn.addEventListener('click', handleClick))
// ✅ 1 listener cho parent
container.addEventListener('click', (e) => {
if (e.target.matches('.button')) handleClick(e)
})3. Debounce + Throttle
// Debounce: user ngừng input mới execute
function debounce(fn, delay) {
let timer
return function(...args) {
clearTimeout(timer)
timer = setTimeout(() => fn.apply(this, args), delay)
}
}
// Throttle: limit frequency
function throttle(fn, delay) {
let lastTime = 0
return function(...args) {
const now = Date.now()
if (now - lastTime >= delay) {
fn.apply(this, args)
lastTime = now
}
}
}6.2 Debug
1. DevTools call stack
function c() {
debugger // Pause ở đây, xem call stack
}2. console.trace()
function trackExecution() {
console.trace('Path') // Output full call stack
}3. Performance analysis
performance.mark('start')
// ... code
performance.mark('end')
performance.measure('Loop perf', 'start', 'end')
const measure = performance.getEntriesByName('Loop perf')[0]
console.log(`Time: ${measure.duration}ms`)6.3 FAQ
| Vấn đề | Lý do | Giải |
|---|---|---|
| Memory cao | Memory leak, cache quá nhiều | Check global, remove listener |
| Page lag | Long task block main thread | Split task, Web Workers |
| Event không trigger | Listener chưa bind, element không tồn tại | Check DOM ready |
| Order async sai | Mix macro + micro | Dùng Promise hoặc async/await |
| Timer không chính xác | Main thread block | Web Workers / requestAnimationFrame |
Tổng kết
- Runtime = engine + env API, runtime khác cấp khả năng khác
- Event loop coordinate sync, microtask, macrotask
- Call stack record function execution, stack overflow do recursion sâu
- GC auto clean variable không dùng, nhưng coi chừng memory leak
- Performance: giảm reflow/repaint, dùng async hợp lý
Hỏi AI thế nào
- "Function chạy chậm, optimize perf giúp"
- "Memory tăng dần, có thể leak, check giúp"
- "Async order sai, phải A trước B, hiện A và B start gần như cùng lúc"
- "Event listener không trigger, check DOM load timing"
2026 cho VN dev
- Bun runtime: thay Node.js, nhanh hơn 3-5x, native TS, all-in-one
- Deno 2: TypeScript native, security default, package từ URL
- Edge runtime: Cloudflare Workers, Vercel Edge — V8 isolate
- Web Workers + SharedArrayBuffer: true multi-threading trong browser
- WebGPU + WASM: heavy compute (AI inference) trong browser
- Performance API: User Timing Level 3, INP (Interaction to Next Paint) 2024+