JavaScript 作用域
本文用于个人学习记录,参考了其它文章,非官方解释,如有错漏,欢迎指正
JS 作用域
在 JavaScript 中,作用域(Scope) 是一个非常重要的概念。它决定了变量、函数和对象的可访问性和生命周期
JavaScript 采用了 词法作用域(Lexical Scope),也就是 静态作用域,这意味着变量的作用域在语法解析时就已经确定了。换句话说,一个变量的作用域取决于它在哪里声明,而不是它在哪里被使用
JS 作用域链
作用域链是 JavaScript 中用于查找变量的一种机制。它由当前作用域和所有父级作用域的变量对象组成。当访问一个变量时,JavaScript 引擎会首先在当前作用域的变量对象中查找,如果找不到,则沿着作用域链向上查找,直到找到变量或者到达全局作用域
在浏览器的 devtool 或者 vscode 中,我们可以通过打断点实时追踪代码执行过程中的作用域链变化情况
JS 作用域的类别
1.Global
在浏览器环境下,Global 作用域即是 window 对象
在 Node 环境下,Global 作用域则是 global 对象
2. Script
只存在与浏览器环境下的作用域,一个 <script> 元素对应一个 Script 作用域,在顶层使用 let, const 声明的变量存放于 Script 作用域中
3. Module
使用 ESM 的情况下特有的作用域,所有在顶层声明的变量和函数都存放于 Module 作用域中
4. Local
Local 作用域即局部作用域,在 JavaScript 中,Local 总是函数中的作用域,所以也可以称为函数作用域
Node 环境下的模块,本质是一个特殊的函数,它包含 exports、require、module、__dirname、__filename 这五个参数
所以 Node 环境下的模块作用域,本质是一个特殊的 Local 作用域,而非 Module 作用域
5. Block
ES6 引入块语句后,每个块语句都会生成一个单独的 Block 作用域
if,while,for 等语句的语句块也会生成 Block 作用域
在这些语句块中,使用 let 和 const 声明的变量会存放在对应的 Block 作用域中
6. Catch block
一个 catch 语句除了生成一个 Block 作用域,还会生成一个特殊的 Catch block 作用域,Catch block 作用域中只存放了一个错误对象
7. With block
一个 with 语句除了生成一个 Block 作用域,还会生成一个特殊的 With block 作用域,Catch block 作用域即是在 with 语句中传入的对象
8. Eval
eval() 函数会生成一个单独的作用域,在 eval 的代码里使用 let 或 const 声明的变量都在这个作用域里
9. Closure
Closure,即闭包,是 JavaScript 中一个非常重要的概念。关于闭包的概念,有很多不同版本的定义,这里例举一些比较权威的定义:
MDN的定义如下:
闭包(closure)是一个函数以及其捆绑的周边环境状态(lexical environment,词法环境)的引用的组合。换而言之,闭包让开发者可以从内部函数访问外部函数的作用域。在 JavaScript 中,闭包会随着函数的创建而被同时创建
《JavaScript 高级程序设计》第四版中是这样定义的:
闭包指的是引用了另一个函数作用域中变量的函数,通常是在嵌套函数中实现的
《JavaScript 权威指南》第六版中则是这样:
从技术的角度讲,所有的 JavaScript 函数都是闭包,它们都是对象,它们都关联到作用域链
emm,老实说有点难以理解为什么有这么多不同的解释...
自己 debugger 看看:
通过调试,我观察的结果是:
假设存在一个函数 f,当其内部函数需要访问 f 中的变量时,JS 引擎便会为 f 创建一个与之同名的闭包,里面存放了所有内部函数会用到的变量,内部函数执行时,通过该闭包来访问 f 中的变量
内部函数可以有多个,但共享同一个闭包
直到所有需要访问 f 中的变量的内部函数销毁之前,f 对应的闭包都会一直存在,即便 f 本身已经销毁