Skip to content

JavaScript Runtime In-Depth Guide

Preface

You've already learned JavaScript basics, but have you ever wondered:

  • Where exactly does your code run?
  • Why does the same code behave differently in the browser versus Node.js?
  • Why does code sometimes "freeze up," while at other times it seems to run "in parallel"?

This article will take you deep into the JavaScript runtime environment, including the event loop, call stack, memory management, and more. After reading this, you'll understand why code executes in a particular order, quickly locate async-related bugs, optimize code performance, and avoid memory leaks.

What will you learn in this article?

ChapterContentWhat you'll be able to do
Chapter 1Runtime overviewUnderstand where JavaScript code runs
Chapter 2Browser runtimeKnow what Web APIs the browser provides
Chapter 3Node.js runtimeUnderstand the server-side JavaScript environment
Chapter 4Event loop deep diveMaster the execution order of macrotasks and microtasks
Chapter 5Call stack and memoryUnderstand code execution and memory management
Chapter 6Practical tipsOptimize performance and debug memory leaks

1. Runtime Overview

🤔 Core Question

What is a "runtime"? JavaScript is just a language — why does the same code behave differently in different environments?

1.1 What is a Runtime?

Runtime = JavaScript Engine + Environment-provided APIs

If JavaScript is the "programming language," then the runtime is the "operating system" — it determines what your code can and cannot do.

┌─────────────────────────────────────┐
│         JavaScript Code             │
├─────────────────────────────────────┤
│      JavaScript Engine (V8)         │  ← Responsible for parsing and executing code
├─────────────────────────────────────┤
│      Runtime Environment (Browser/Node.js) │  ← Provides additional capabilities
└─────────────────────────────────────┘

An analogy: JavaScript is "Mandarin," the runtime is the "city"

  • JavaScript syntax (Mandarin) is the same everywhere
  • But different cities provide different facilities:
    • Browser = has DOM, window, fetch (like a city with malls, libraries)
    • Node.js = has fs, http, path (like a city with factories, highways)

1.2 Two Mainstream Runtimes

FeatureBrowserNode.js
Primary useWeb interaction, user interfacesServer-side applications, CLI tools
Global objectwindowglobal
DOM API✅ Supported❌ Not supported
File system❌ Limited✅ Full support
Module systemES ModulesCommonJS + ES Modules
TimerssetTimeout, setIntervalsetTimeout, setInterval
Network requestsfetch, XMLHttpRequesthttp, https modules

👇 Try it out: Compare the environment differences between the browser and 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 完全不同——这就是"环境判断"的重要性。

💡 Core Takeaway

The runtime determines which APIs you can use. DOM APIs available in the browser won't work in Node.js; file APIs available in Node.js won't work in the browser. That's why some code needs "environment detection."


2. Browser Runtime

🤔 Core Question

What capabilities does the browser provide for JavaScript to manipulate web pages?

2.1 Components of the Browser Runtime

┌─────────────────────────────────────────────┐
│            JavaScript Engine                │
│            (V8 / SpiderMonkey)              │
└─────────────────────────────────────────────┘

┌─────────────────────────────────────────────┐
│              Web APIs                        │
│  ┌─────────┐ ┌──────────┐ ┌──────────┐     │
│  │   DOM   │ │   BOM    │ │ Network  │     │
│  │Manipulate│ │Manipulate│ │ Network  │     │
│  │   pages  │ │ browser  │ │ requests │     │
│  └─────────┘ └──────────┘ └──────────┘     │
└─────────────────────────────────────────────┘

┌─────────────────────────────────────────────┐
│           Event Loop                        │
│     Coordinates code execution, event       │
│     handling, and task scheduling            │
└─────────────────────────────────────────────┘

2.2 Three Categories of Web APIs

1. DOM API - Manipulate page content

javascript
// Find elements
const title = document.querySelector('h1')

// Modify content
title.textContent = 'New Title'

// Add styles
title.style.color = 'red'

2. BOM API - Manipulate the browser

javascript
// Page navigation
window.location.href = 'https://example.com'

// Browser storage
localStorage.setItem('key', 'value')

// Browser history
history.back()

3. Network API - Network requests

javascript
// Send HTTP request
fetch('/api/data')
  .then(response => response.json())
  .then(data => console.log(data))

2.3 Browser-Specific Event Mechanism

One of the most powerful features of the browser runtime is "event-driven" programming — code doesn't need to run continuously, but executes when the user performs actions.

javascript
button.addEventListener('click', () => {
  console.log('Button was clicked')
})

Common event types:

Event typeWhen triggeredPractical scenario
clickMouse clickButton interaction
inputInput field content changesReal-time search
scrollPage scrollingLazy loading
loadResource finished loadingInitialize data
errorError occurredError handling

3. Node.js Runtime

🤔 Core Question

What enables JavaScript to run on the server side?

3.1 Components of Node.js

┌─────────────────────────────────────────────┐
│            JavaScript Engine                │
│                 (V8)                        │
└─────────────────────────────────────────────┘

┌─────────────────────────────────────────────┐
│           Node.js Built-in Modules           │
│  ┌─────────┐ ┌──────────┐ ┌──────────┐     │
│  │   fs    │ │   http   │ │   path   │     │
│  │  File   │ │  HTTP    │ │  Path    │     │
│  │operations│ │  server  │ │ handling │     │
│  └─────────┘ └──────────┘ └──────────┘     │
└─────────────────────────────────────────────┘

┌─────────────────────────────────────────────┐
│          libuv Event Loop Library           │
│      Cross-platform async I/O support       │
└─────────────────────────────────────────────┘

3.2 Node.js-Specific Capabilities

1. File System Operations

javascript
const fs = require('fs')

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

// Write file
fs.writeFile('./output.txt', 'Hello', (err) => {
  if (err) throw err
  console.log('Write successful')
})

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 approach)
import fs from 'fs'
export { myFunction }

3.3 Browser vs Node.js Comparison

FeatureBrowserNode.js
Entry fileHTML fileJavaScript file
Global objectswindow, documentglobal, process
Module loading<script> tagsrequire() / import
SecuritySandbox environment, restrictedCan access system resources
Use caseUser interfacesBackend services, tools

4. Event Loop Deep Dive

🤔 Core Question

JavaScript is single-threaded — how does it achieve "non-blocking" behavior?

4.1 What is the Event Loop?

Event Loop = JavaScript's "task scheduling center"

JavaScript is single-threaded and can only do one thing at a time. But the event loop makes it appear to do many things "simultaneously."

Core mechanism:

  1. Execute synchronous code (call stack)
  2. Process asynchronous tasks (task queues)
  3. Wait for new tasks (loop continuously)
Call Stack                 Task Queue
┌─────────┐              ┌──────────┐
│ Task 1  │              │ Macro 1  │
│ Task 2  │ ←──────────── │ Macro 2  │
│ Task 3  │   After one    │ Macro 3  │
└─────────┘   completes,   └──────────┘
      ↓        take next         ↑
      └──────────────────────────┘
         Event loop checks continuously

4.2 Macrotasks vs Microtasks

This is the concept most easily confused in interviews and actual development!

Macrotasks:

  • setTimeout, setInterval
  • I/O operations
  • UI rendering

Microtasks:

  • Promise.then
  • MutationObserver
  • queueMicrotask

Execution order: Synchronous code → Microtasks → Macrotasks

👇 Try it out: Observe the execution order of macrotasks and microtasks

任务队列:宏任务 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 Classic Interview Question

javascript
console.log('1')

setTimeout(() => console.log('2'), 0)

Promise.resolve().then(() => console.log('3'))

console.log('4')

// Output: 1, 4, 3, 2

Why this order?

  1. Execute synchronous code: console.log('1'), console.log('4') → outputs 1, 4
  2. Check microtask queue: Promise.then → outputs 3
  3. Check macrotask queue: setTimeout → outputs 2

💡 Practical Tips

  • If you want code to execute as soon as possible, use microtasks (Promise.then)
  • If you want delayed execution, use macrotasks (setTimeout)
  • Never mix too many async operations, or you'll fall into "callback hell"

5. Call Stack and Memory

🤔 Core Question

How is code executed? Where are variables stored? When are they garbage collected?

5.1 Call Stack: The "Footprint" of Function Execution

Call stack = A "notebook" that records function calls

Every time you call a function, a new record is added to the stack; when the function finishes, the record is removed.

javascript
function a() {
  b()
}

function b() {
  c()
}

function c() {
  console.log('Execution complete')
}

a()

Call stack changes:

Step 1: Call a()
┌─────────┐
│    a    │
└─────────┘

Step 2: a() calls b()
┌─────────┐
│    b    │
│    a    │
└─────────┘

Step 3: b() calls c()
┌─────────┐
│    c    │
│    b    │
│    a    │
└─────────┘

Step 4: c() completes, pop in order
┌─────────┐
│    b    │
│    a    │
└─────────┘

👇 Try it out: Observe the call stack changes

调用栈:函数执行的足迹

代码

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

调用栈

栈底
栈为空
栈顶

当前状态:

调用 main()

输出

等待输出...

调用栈工作原理:

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

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

5.2 Memory Management: Where Does the Garbage Go?

JavaScript has an "automatic garbage collection" mechanism — you don't need to manually free memory; the engine does it for you.

Garbage collection principle: Mark-and-Sweep algorithm

  1. Mark phase: Starting from "roots," find all reachable variables
  2. Sweep phase: Unmarked variables are "garbage" and will be collected
javascript
// Garbage collection example
let obj1 = { name: 'Object 1' }
let obj2 = { name: 'Object 2' }

// obj1 is reassigned, the original object loses its reference
obj1 = null  // The original { name: 'Object 1' } will be collected

// obj2 is still in use, won't be collected
console.log(obj2.name)

👇 Try it out: Observe the garbage collection process

垃圾回收机制

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

对象引用关系

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

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

1
标记阶段

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

2
清除阶段

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

3
重置标记

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

核心要点
  • 根对象(Root): 全局变量、栈上的变量等,总是被认为是可达的
  • 可达对象: 从根对象出发,通过引用链能访问到的对象
  • 垃圾对象: 无法从根对象访问到的对象,会被回收
  • 循环引用: 如果两个对象互相引用但都不可达,仍会被回收

实际应用技巧

💡
及时解除引用

对象不再使用时,将其设为 null

🔒
避免意外的全局变量

使用 const/let 代替 var

🧹
清理事件监听

组件销毁时移除所有监听器

📊
定期检查内存

用 DevTools Memory 面板监控

5.3 Memory Leaks: The Consequences of Forgetting to Clean Up

Memory leak = Memory that should be freed isn't freed, accumulating over time

Common causes:

1. Too many global variables

javascript
// ❌ Wrong: Global variables won't be collected
globalCache = []

function addItem(item) {
  globalCache.push(item)
}

2. Event listeners not removed

javascript
// ❌ Wrong: Listener not removed
button.addEventListener('click', handleClick)

// ✅ Correct: Remove listener when no longer needed
button.removeEventListener('click', handleClick)

3. Closures referencing large objects

javascript
// ❌ Wrong: Closure keeps referencing large object, won't be collected
function createHandler() {
  const bigData = new Array(1000000).fill('data')
  return function() {
    console.log('Processing')
  }
}

const handler = createHandler()  // bigData persists in memory

👇 Try it out: Observe how memory leaks occur

内存泄漏演示

内存使用情况0%

全局变量泄漏

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

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

全局变量 (0 项)
暂无全局变量
❌ 错误做法
// 全局变量不会被回收
globalCache = []
function addItem() {
  globalCache.push(largeData)
}

如何避免内存泄漏

  • 避免全局变量: 使用 const/let 代替 var,尽量使用局部变量
  • 及时清理监听器: 组件销毁时移除所有事件监听
  • 释放闭包引用: 不需要时将闭包变量设为 null
  • 使用 WeakMap/WeakSet: 自动清理不再被引用的对象
  • 定期检查: 用 DevTools Memory 面板检查内存泄漏

💡 Practical Tips

  • Regular checks: Open browser DevTools → Memory → Take Heap Snapshot to view memory usage
  • Avoid global variables: Use const and let, not var
  • Clean up promptly: Remove event listeners and timers when done
  • Weak references: Use WeakMap and WeakSet to store object references

6. Practical Tips

🤔 Core Question

How do you write high-performance JavaScript code? How do you debug problems?

6.1 Performance Optimization Tips

1. Reduce reflows and repaints

javascript
// ❌ Wrong: Triggers reflow on every loop iteration
for (let i = 0; i < 1000; i++) {
  element.style.top = i + 'px'
}

// ✅ Correct: Batch modification
element.style.transform = `translateY(${position}px)`

2. Use event delegation

javascript
// ❌ Wrong: Add listener to every button
buttons.forEach(btn => {
  btn.addEventListener('click', handleClick)
})

// ✅ Correct: Add only one listener to parent element
container.addEventListener('click', (e) => {
  if (e.target.matches('.button')) {
    handleClick(e)
  }
})

3. Debounce and throttle

javascript
// Debounce: Execute after user stops typing
function debounce(fn, delay) {
  let timer
  return function(...args) {
    clearTimeout(timer)
    timer = setTimeout(() => fn.apply(this, args), delay)
  }
}

// Throttle: Limit execution 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 Debugging Tips

1. View call stack with DevTools

javascript
function a() {
  b()
}

function b() {
  c()
}

function c() {
  debugger  // Pause here to view call stack
}

a()

2. Trace execution path with console.trace()

javascript
function trackExecution() {
  console.trace('Execution path')
  // Will output the complete call stack
}

3. Analyze performance with the Performance API

javascript
performance.mark('start')

// Execute some code
for (let i = 0; i < 10000; i++) {
  // ...
}

performance.mark('end')
performance.measure('Loop performance', 'start', 'end')

const measure = performance.getEntriesByName('Loop performance')[0]
console.log(`Execution time: ${measure.duration}ms`)

6.3 Common Problems Quick Reference

ProblemPossible causeSolution
High memory usageMemory leak, too much cachingCheck global variables, remove listeners
Page stutteringLong tasks blocking main threadSplit tasks, use Web Workers
Events not firingListener not bound, element doesn't existCheck DOM loading timing
Async order incorrectMixing macrotasks and microtasksUse Promise or async/await consistently
Timer inaccuracyMain thread blockedUse Web Workers or requestAnimationFrame

Summary

You should now be able to understand:

  • Runtime = Engine + Environment APIs — different runtimes provide different capabilities
  • Event loop coordinates the execution order of synchronous code, microtasks, and macrotasks
  • Call stack records the function execution process — stack overflow occurs from too-deep recursion
  • Garbage collection automatically cleans up unused variables, but watch out for memory leaks
  • Performance optimization hinges on reducing reflows/repaints and using async appropriately

💡 When encountering problems, tell your AI this way

  • "This function executes too slowly, help me optimize the performance"
  • "Memory usage keeps growing, might be a memory leak, help me check"
  • "Async operations are in the wrong order — should be A then B, but A and B start almost simultaneously"
  • "Event listener isn't firing, check if the element has already been loaded into the DOM"