主题
作用域与作用域链
作用域用于规定“一个标识符在什么范围内可访问”。JavaScript 采用词法作用域,变量的可访问范围主要取决于代码写在什么位置,而不是函数在什么位置被调用。
什么是作用域
可以把作用域理解为一套查找规则:
- 当前代码能访问哪些变量。
- 变量名冲突时,优先使用哪一层。
- 离开某个代码块或函数后,哪些变量不再可见。
js
const globalName = 'global'
function outer() {
const outerName = 'outer'
function inner() {
const innerName = 'inner'
console.log(globalName, outerName, innerName)
}
inner()
}inner 内部可以访问 innerName、outerName 和 globalName,这是因为它会沿着作用域链逐层向外查找。
常见作用域层级
全局作用域
最外层的作用域。浏览器里通过 script 顶层声明的变量通常会处于全局环境中。
js
const siteName = 'vfan docs'函数作用域
函数内部声明的变量只在当前函数内可见。
js
function sum(a, b) {
const total = a + b
return total
}
console.log(sum(1, 2))
// console.log(total) // total is not defined块级作用域
let、const 和 class 会形成块级作用域,常见于 if、for、while、独立花括号代码块。
js
if (true) {
const message = 'hello'
console.log(message)
}
// console.log(message) // message is not defined作用域链是什么
当读取一个变量时,JavaScript 会按如下顺序查找:
- 先找当前作用域。
- 当前没有,就找外层作用域。
- 一直找到全局作用域。
- 仍然找不到,才抛出错误。
js
const name = 'global'
function foo() {
const name = 'foo'
function bar() {
console.log(name)
}
bar()
}
foo() // foobar 查找 name 时,先看自己内部,没有;然后去外层 foo 作用域找到,所以输出 foo。
词法作用域
JavaScript 的作用域在代码编写阶段就已经确定了,和调用位置无关。
js
const name = 'global'
function showName() {
console.log(name)
}
function run() {
const name = 'local'
showName()
}
run() // globalshowName 的外层作用域是全局,不是 run,因此输出 global。
暂时性死区
let 和 const 在块级作用域内会存在暂时性死区,声明前不能访问。
js
{
// console.log(count) // Cannot access 'count' before initialization
const count = 1
}这不是“没声明”,而是“已经进入作用域,但还没完成初始化”。
常见误区
var 没有块级作用域
js
if (true) {
var value = 1
}
console.log(value) // 1for 循环中的 let 会为每次迭代创建新绑定
js
for (let i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i)
}, 0)
}
// 0 1 2和闭包的关系
闭包本质上就是函数保留了对其词法作用域的访问能力。理解作用域链之后,再看闭包会更自然。
