JavaScript 执行机制:作用域、闭包、原型链与事件循环
⚙️ 从代码如何查找变量、函数如何保留上下文,到对象如何继承、异步任务如何调度,串起 JavaScript 的核心运行机制。
1. 执行上下文与调用栈
JavaScript 执行代码时会创建执行上下文,包括变量环境、词法环境和 this 绑定。全局代码先进入栈,函数调用时创建新的函数执行上下文;函数返回后出栈。
function first() {
second()
}
function second() {
console.log('run')
}
first()
// 调用栈:global → first → second
递归过深会产生 Maximum call stack size exceeded。
2. 作用域与作用域链
- 词法作用域:变量可见性由函数定义位置决定,而非调用位置。
let、const具有块级作用域,并存在暂时性死区。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 完成后将回调放入任务队列;调用栈清空后,事件循环按规则调度。
优先级概览:
- 执行当前同步代码
- 清空微任务队列:
Promise.then、queueMicrotask - 浏览器可能进行渲染
- 执行一个宏任务:
setTimeout、事件、消息任务 - 再次清空微任务
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 的事件循环阶段并不完全相同。
- 箭头函数没有自己的
this、arguments和prototype。
自检清单
- 能画出一段代码的调用栈与作用域链
- 能解释闭包保留变量的原因
- 能区分实例属性、原型属性与静态属性
- 能判断同步、微任务、宏任务的输出顺序
- 能解释长任务为何导致页面卡顿