跳到主要内容

JavaScript 执行机制:作用域、闭包、原型链与事件循环

⚙️ 从代码如何查找变量、函数如何保留上下文,到对象如何继承、异步任务如何调度,串起 JavaScript 的核心运行机制。

1. 执行上下文与调用栈

JavaScript 执行代码时会创建执行上下文,包括变量环境、词法环境和 this 绑定。全局代码先进入栈,函数调用时创建新的函数执行上下文;函数返回后出栈。

function first() {
second()
}
function second() {
console.log('run')
}
first()
// 调用栈:global → first → second

递归过深会产生 Maximum call stack size exceeded

2. 作用域与作用域链

  • 词法作用域:变量可见性由函数定义位置决定,而非调用位置。
  • letconst 具有块级作用域,并存在暂时性死区。
  • var 具有函数作用域,声明会提升,初始化为 undefined
  • 查找变量时从当前词法环境逐级向外,直到全局环境。
const value = 'global'
function outer() {
const value = 'outer'
return function inner() {
console.log(value)
}
}
outer()() // outer

3. 闭包

闭包是“函数 + 定义时可访问的词法环境”。只要内部函数仍可达,相关变量就不会被回收。

常见用途:

  • 封装私有状态
  • 函数柯里化与参数预绑定
  • React Hooks、事件处理器和缓存
function createCounter() {
let count = 0
return () => ++count
}
const next = createCounter()
next() // 1
next() // 2

⚠️ 闭包本身不是内存泄漏;只有不再需要的闭包仍被事件监听器、定时器或全局引用持有时,才会阻止垃圾回收。

4. 原型链

对象通过内部 [[Prototype]] 关联另一个对象。读取属性时,先查找自身属性,再沿原型链向上查找,直到 null

function User(name) {
this.name = name
}
User.prototype.sayHi = function () {
return `Hi, ${this.name}`
}
const user = new User('Ada')
user.sayHi()

new User() 的主要步骤:创建对象、连接到 User.prototype、以新对象为 this 调用构造函数、返回对象。

class 是基于原型机制的语法封装,不是独立的继承模型。

5. 事件循环

同步任务在调用栈中执行。异步 API 完成后将回调放入任务队列;调用栈清空后,事件循环按规则调度。

优先级概览:

  1. 执行当前同步代码
  2. 清空微任务队列:Promise.thenqueueMicrotask
  3. 浏览器可能进行渲染
  4. 执行一个宏任务:setTimeout、事件、消息任务
  5. 再次清空微任务
console.log(1)
setTimeout(() => console.log(2))
Promise.resolve().then(() => console.log(3))
console.log(4)
// 1 4 3 2

易错点

  • 微任务链过长会阻塞渲染和后续宏任务。
  • setTimeout(fn, 0) 只表示达到最短等待时间,不保证立即执行。
  • 浏览器和 Node.js 的事件循环阶段并不完全相同。
  • 箭头函数没有自己的 thisargumentsprototype

自检清单

  • 能画出一段代码的调用栈与作用域链
  • 能解释闭包保留变量的原因
  • 能区分实例属性、原型属性与静态属性
  • 能判断同步、微任务、宏任务的输出顺序
  • 能解释长任务为何导致页面卡顿