TLDR
Javascript具有词汇(也称为静态)作用域和闭包。这意味着您可以通过查看源代码来确定标识符的范围。
四个范围是:
- 全球-一切可见
- 功能-在功能(及其子功能和块)中可见
- 块-在块(及其子块)中可见
- 模块-在模块内可见
在全局范围和模块范围的特殊情况之外,使用
var(函数范围),
let(块范围)和
const(块范围)声明变量。标识符声明的大多数其他形式在严格模式下具有块作用域。
总览
范围是代码库中标识符有效的区域。
词汇环境是标识符名称和与其关联的值之间的映射。
范围由词汇环境的链接嵌套组成,嵌套中的每个级别对应于祖先执行上下文的词汇环境。
这些链接的词汇环境形成范围“链”。标识符解析是沿着此链搜索匹配标识符的过程。
标识符解析仅在一个方向上发生:向外。这样,外部词汇环境无法“看到”内部词汇环境。
确定 Javascript中标识符范围三个相关因素:
- 标识符如何声明
- 声明标识符的地方
- 处于[严格模式]
可以声明标识符的一些方式:
var
,let
和const
- 功能参数
- 捕获块参数
- 函数声明
- 命名函数表达式
- 全局对象上的隐式定义的属性(即
var
在非严格模式下丢失) import
陈述eval
可以声明一些位置标识符:
- 全球背景
- 功能体
- 普通块
- 控制结构的顶部(例如循环,if,while等)
- 控制结构体
- 模组
声明样式
变种
使用声明的标识符
var具有函数scope
,除了直接在全局上下文中声明它们时,在这种情况下,它们作为属性添加到全局对象上并具有全局范围。在
eval功能中使用它们有单独的规则。
let和const
使用
let和声明的标识符
const具有块作用域 ,除了直接在全局上下文中声明时(在这种情况下,它们具有全局作用域)。
注:
let,
const并
var
都悬挂。这意味着它们的逻辑定义位置是其包围范围(模块或功能)的顶部。但是,在控制已通过源代码中的声明点之前,声明为使用
let且
const无法读取或分配给它们的变量。过渡期称为时间盲区。
function f() { function g() { console.log(x) } let x = 1 g()}f() // 1 because x is hoisted even though declared with `let`!功能参数名称
函数参数名称的作用域为函数主体。注意,这有点复杂。声明为默认参数的函数将关闭参数列表,而不是函数的主体。
函数声明
函数声明在严格模式下具有块作用域,在非严格模式下具有函数作用域。注意:非严格模式是基于不同浏览器古怪的历史实现的一组复杂的紧急规则。
命名函数表达式
命名函数表达式的作用域为自身(例如,出于递归目的)。
全局对象上的隐式定义的属性
在非严格模式下,全局对象上的隐式定义的属性具有全局范围,因为全局对象位于范围链的顶部。在严格模式下,这些是不允许的。
评估
在
eval字符串中,使用声明的变量
var将放置在当前作用域中,或者如果
eval被间接使用,则用作全局对象的属性。
例子
下面将抛出的ReferenceError因为名字
x,
y并
z有功能没有意义之外
f。
function f() { var x = 1 let y = 1 const z = 1}console.log(typeof x) // undefined (because var has function scope!)console.log(typeof y) // undefined (because the body of the function is a block)console.log(typeof z) // undefined (because the body of the function is a block)下面将抛出的ReferenceError为
y和
z,但不适合
x,因为知名度
x不被约束块。定义控制结构的体块一样
if,
for和
while,行为类似。
{ var x = 1 let y = 1 const z = 1}console.log(x) // 1console.log(typeof y) // undefined because `y` has block scopeconsole.log(typeof z) // undefined because `z` has block scope在下面,
x由于
var具有函数作用域,因此在循环外部可见:
for(var x = 0; x < 5; ++x) {}console.log(x) // 5 (note this is outside the loop!)…由于这种行为,您需要注意关闭使用
varin循环声明的变量。
x此处声明的变量只有一个实例,并且在逻辑上位于循环之外。
以下打印了
5五次,然后在循环外部打印
5了第六次
console.log:
for(var x = 0; x < 5; ++x) { setTimeout(() => console.log(x)) // closes over the `x` which is logically positioned at the top of the enclosing scope, above the loop}console.log(x) // note: visible outside the loop打印以下内容,
undefined因为它们
x是块作用域的。回调是异步进行的。新行为
let变量意味着每个匿名函数关闭了一个名为不同的变量
x(不像它会用做
var),所以整数
0通过
4印:
for(let x = 0; x < 5; ++x) { setTimeout(() => console.log(x)) // `let` declarations are re-declared on a per-iteration basis, so the closures capture different variables}console.log(typeof x) // undefined以下内容将不会引发,
ReferenceError因为
x该块的可见性不受该块的限制。但是它将打印,
undefined因为变量尚未初始化(由于该
if语句)。
if(false) { var x = 1}console.log(x) // here, `x` has been declared, but not initialised在
for循环顶部使用声明的变量的
let作用域为循环的主体:
for(let x = 0; x < 10; ++x) {}console.log(typeof x) // undefined, because `x` is block-scopedReferenceError由于
x该块的可见性受到限制,因此以下内容将引发a :
if(false) { let x = 1}console.log(typeof x) // undefined, because `x` is block-scoped使用的变量声明
var,
let或
const都作用域模块:
// module1.jsvar x = 0export function f() {}//module2.jsimport f from 'module1.js'console.log(x) // throws ReferenceError以下将在全局对象上声明一个属性,因为
var在全局上下文中使用声明的变量将作为属性添加到全局对象:
var x = 1console.log(window.hasOwnProperty('x')) // truelet并且
const在全局上下文中不向全局对象添加属性,但仍具有全局范围:
let x = 1console.log(window.hasOwnProperty('x')) // false函数参数可以认为是在函数体中声明的:
function f(x) {}console.log(typeof x) // undefined, because `x` is scoped to the function捕获块参数的作用域为捕获块主体:
try {} catch(e) {}console.log(typeof e) // undefined, because `e` is scoped to the catch block命名函数表达式仅被表达到表达式本身:
(function foo() { console.log(foo) })()console.log(typeof foo) // undefined, because `foo` is scoped to its own expression在非严格模式下,全局对象上隐式定义的属性是全局范围的。在严格模式下,您会得到一个错误。
x = 1 // implicitly defined property on the global object (no "var"!)console.log(x) // 1console.log(window.hasOwnProperty('x')) // true在非严格模式下,函数声明具有函数范围。在严格模式下,它们具有块作用域。
'use strict'{ function foo() {}}console.log(typeof foo) // undefined, because `foo` is block-scoped它是如何工作的
范围定义为标识符在其上有效的代码的词法区域。
在Javascript中,每个功能对象都有一个隐藏的
[[Environment]]引用,该引用是对在其中创建它的执行上下文(堆栈框架)的词汇环境的引用。
调用函数时,将调用隐藏
[[Call]]方法。此方法创建一个新的执行上下文,并在新的执行上下文和功能对象的词法环境之间建立链接。通过将
[[Environment]]功能对象上的值复制到新执行上下文的词法环境上的外部引用字段中,可以完成此操作。
注意,新执行上下文和函数对象的词法环境之间的这种链接称为闭包。
因此,在Javascript中,作用域是通过外部引用在“链”中链接在一起的词法环境实现的。这种词汇环境链称为作用域链,并且通过在链中搜索匹配的标识符来进行标识符解析。



