JavaScript 作用域(Scope)
词法作用域 (Lexical Scope)
JavaScript 采用的是词法作用域,也叫静态作用域。这意味着变量的作用域在代码编写阶段(词法分析阶段)就已经确定了,而不是在代码运行阶段。内部函数可以访问其外部(父级)函数的作用域链,但反之则不行。在 JavaScript 中,函数嵌套(内部函数)会继承外部函数的作用域链。与下面作用域不同,这是一个核心规则不是具体作用域。
全局作用域 (Global Scope)
在所有函数或块之外声明的变量拥有全局作用域。全局作用域下声明的变量可以在 JavaScript 脚步的任何地方被访问到(包括函数内部)。在浏览器环境中,全局作用域通常指 window 的顶层作用域,但是除此之外还有 WorkGlobalScope,DedicatedWorkerGlobalScope,ServiceWorkerGlobalScope,SharedWorkerGlobalScope 等其他全局作用域。
函数作用域 (Function Scope / Local Scope)
函数内部声明的变量拥有函数作用域,也称为局部作用域。这些变量只能在该函数内部访问,函数外部无法访问。var 声明的变量具有函数作用域。
注意:部分浏览器会将块内 function 声明 “提升” 到块所在的函数作用域 / 全局作用域(类似 var),而非严格限制在块内(例如:if (true) { function fn() {} } fn() 在 Chrome 中可执行)
块级作用域 (Block Scope)
ES6 (ECMAScript 2015) 引入了块级作用域。使用 let, const, function(严格模式下), class 声明的变量和常量具有块级作用域。块级作用域由一对花括号 {}(例如 if 语句、for 循环或单纯的代码块)创建,变量只能在该代码块内部访问。
块级作用域中 let, const, function(严格模式下), class 声明的变量存在暂时性死区(TDZ),声明前无法访问,且无变量提升(或说‘不完全提升’)
模块作用域 (Module Scope)
在 ES6 模块中声明的变量具有模块作用域。它们只在该模块文件内部可用,不会自动成为全局变量。
闭包作用域 (Closure Scope)
什么是闭包?闭包是一个函数和该函数的词法作用域的组合。即:函数 + 词法作用域。闭包,并非一个独立作用域,而是 “作用域的延伸与保留”。外层函数在执行完本该就结束了,其中的变量本该释放了,但是通过闭包,变量的生命周期得以延长。
eval() 作用域 (Eval Scope)
- 非严格模式下 会使用当前的作用域 并可能修改当前作用域
- 严格模式下 eval 执行的代码会被限制在 独立的临时作用域 内,不会修改外部作用域
执行动态代码建议使用 Function 等方式。由于 js 引擎无法提前进行优化,性能下降,此外执行的动态代码更容易出现恶意代码,不建议执行动态代码。其他还有可读性差等问题不建议使用 eval() 。
eval() 当下使用场景主要是执行用户给的代码,比如代码编辑器等。
with 块作用域 (With Block Scope)
- 非严格模式下 with 语句的作用是 临时将一个对象 “加入” 当前作用域链的顶部,本质是动态修改作用域
- 严格模式无法使用 with 语法
已经被废弃,性能差,不建议使用。
with 在一些微前端框架下,被用来作沙箱使用。
script 脚本作用域 (Script Scope)
普通脚本作用域(<script> 无 type="module")是全局作用域的一种表现形式:var 声明的变量会成为全局对象的属性,let/const 声明的变量仅在当前脚本内全局可访问(不挂载全局对象);模块脚本(type="module")遵循模块作用域,而非脚本作用域