Skip to content

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ươngNội dung
1Runtime overview
2Browser runtime
3Node.js runtime
4Event loop sâu
5Call stack + memory
6Thự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

FeatureBrowserNode.js
Use chínhWeb interaction, UIServer, CLI tool
Global objectwindowglobal
DOM API
File system (hạn chế) Full
ModuleES ModulesCommonJS + ES Modules
TimersetTimeout, setIntervalY vậy
Networkfetch, XMLHttpRequesthttp, https module

👇 So sánh env browser + Node.js:

运行时环境对比

浏览器环境

window
浏览器全局对象
window.location.href
document
DOM 操作
document.querySelector("h1")
localStorage
本地存储
localStorage.setItem("key", "value")
fetch
网络请求
fetch("/api/data")
setTimeout
定时器
setTimeout(() => {}, 1000)
特点:
  • ✅ 有 DOM 和 BOM API,可以操作网页
  • ✅ 有 Web Storage (localStorage, sessionStorage)
  • ✅ 有 fetch 和 XMLHttpRequest 进行网络请求
  • ❌ 没有文件系统访问权限
  • ❌ 不能直接创建 HTTP 服务器

代码演示:不同环境的差异

🌐浏览器结果
点击"在浏览器运行"查看结果
🟢Node.js 结果
需要在 Node.js 环境中运行

核心区别:

浏览器运行时专注于用户界面和网页交互,提供 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

javascript
const title = document.querySelector('h1')
title.textContent = 'Title mới'
title.style.color = 'red'

2. BOM API - op browser

javascript
window.location.href = 'https://example.com'
localStorage.setItem('key', 'value')
history.back()

3. Network API

javascript
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.

javascript
button.addEventListener('click', () => {
  console.log('Button clicked')
})
EventTriggerUse
clickMouse clickButton
inputInput changeSearch realtime
scrollPage scrollLazy load
loadResource loadedInit data
errorLỗiError 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

javascript
const fs = require('fs')
fs.readFile('./data.txt', 'utf8', (err, data) => {
  if (err) throw err
  console.log(data)
})

2. HTTP server

javascript
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

javascript
// 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

FeatureBrowserNode.js
EntryHTML fileJS file
Globalwindow, documentglobal, process
Module load<script>require() / import
SecuritySandbox, hạn chếAccess system resource
UseUIBackend, 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:

  1. Execute sync code (call stack)
  2. Xử async task (task queue)
  3. 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ục

4.2 Macrotask vs Microtask

Đây là concept hay nhầm nhất!

Macrotask:

  • setTimeout, setInterval
  • I/O
  • UI render

Microtask:

  • Promise.then
  • MutationObserver
  • queueMicrotask

Order: sync code → microtask → macrotask

👇 Quan sát order:

任务队列:宏任务 vs 微任务

代码示例

1console.log("1")同步
2setTimeout(() => console.log("2"), 0)宏任务
3Promise.resolve().then(() => console.log("3"))微任务
4console.log("4")同步
5setTimeout(() => console.log("5"), 0)宏任务

调用栈 (正在执行)

执行 console.log("1")

微任务队列 Microtask

队列为空

宏任务队列 Macrotask

队列为空

输出日志 (执行顺序)

等待输出...

执行顺序规则

1执行所有同步代码
2执行微任务队列中的所有任务
3执行一个宏任务
4重复步骤 2-3

核心要点: 微任务优先级高于宏任务。每次执行完一个宏任务后,都会检查并执行所有微任务,然后再执行下一个宏任务。

4.3 Câu phỏng vấn kinh điển

javascript
console.log('1')
setTimeout(() => console.log('2'), 0)
Promise.resolve().then(() => console.log('3'))
console.log('4')

// Output: 1, 4, 3, 2

Lý do:

  1. Sync code: 1, 4
  2. Microtask: Promise.then → 3
  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

javascript
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  │          └─────┘
                                      └─────┘

调用栈:函数执行的足迹

代码

1main()
2function a() {
3function b() {
4function c() {
5console.log("执行完毕")
6}
7}
8}
9}

调用栈

栈底
栈为空
栈顶

当前状态:

调用 main()

输出

等待输出...

调用栈工作原理:

  • 每次调用函数,就会在栈上"压入"一个新的"栈帧"
  • 栈帧记录了函数的执行状态、局部变量等信息
  • 函数执行完毕,栈帧就会从栈上"弹出"
  • 栈是"后进先出"(LIFO)的数据结构
  • 如果递归太深,会导致"栈溢出"错误

调用栈就像一摞盘子:最后放上去的盘子最先被取走。每个函数就是一个盘子,执行完就取走,然后继续执行下面的函数。

5.2 Memory management

JS có auto garbage collection — không cần free memory thủ công.

Mark-Sweep algorithm:

  1. Mark: từ "root" tìm mọi variable accessible
  2. Sweep: variable không mark = "rác" → recycle
javascript
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

垃圾回收机制

标记阶段从根对象开始,标记所有可达对象
清除阶段回收未标记的对象

对象引用关系

未标记
已标记(可达)
已回收
🌳
Root
📦
obj1
📦
obj2
📦
obj3
📦
obj4
📦
obj5
📦
obj6
当前操作:从根对象开始标记

标记-清除算法 (Mark-and-Sweep)

1
标记阶段

从根对象(Root)开始,遍历所有可达对象,标记为"活动对象"

2
清除阶段

遍历整个堆内存,回收所有未被标记的对象

3
重置标记

清除所有标记位,为下一次垃圾回收做准备

核心要点
  • 根对象(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

javascript
// ❌ Global không recycle
globalCache = []
function addItem(item) { globalCache.push(item) }

2. Event listener không remove

javascript
// ❌
button.addEventListener('click', handleClick)

// ✅
button.removeEventListener('click', handleClick)

3. Closure reference object lớn

javascript
// ❌ 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

内存泄漏演示

内存使用情况0%

全局变量泄漏

问题:全局变量不会被垃圾回收,会一直占用内存

示例:不断往全局数组添加数据,从不清理

全局变量 (0 项)
暂无全局变量
❌ 错误做法
// 全局变量不会被回收
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ông var
  • Clean kịp thời: event listener + timer xong remove
  • Weak reference: WeakMap, WeakSet cho object reference

6. Thực chiến

6.1 Performance

1. Giảm reflow/repaint

javascript
// ❌ 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

javascript
// ❌ 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

javascript
// 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

javascript
function c() {
  debugger  // Pause ở đây, xem call stack
}

2. console.trace()

javascript
function trackExecution() {
  console.trace('Path')  // Output full call stack
}

3. Performance analysis

javascript
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ý doGiải
Memory caoMemory leak, cache quá nhiềuCheck global, remove listener
Page lagLong task block main threadSplit task, Web Workers
Event không triggerListener chưa bind, element không tồn tạiCheck DOM ready
Order async saiMix macro + microDùng Promise hoặc async/await
Timer không chính xácMain thread blockWeb 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+