可以阅读@冴羽大神的博客JavaScript 深入之变量对象
执行上下文(
execution context
):当JavaScript
代码执行一段可执行代码(executable code
)时,其所创建的对应的执行环境。可执行代码包括全局代码
、函数代码
、eval 代码
,所以也分成全局执行上下文
、函数执行上下文
、eval 执行上下文
每个执行上下文中包含了三部分:
Variable Object, OV
):与执行上下文相关的数据作用域,存储了在上下文中定义的变量和函数声明。在函数执行上下文中又叫做激活对象(Activated Object, AO
)。Scope Chain
):由多个执行上下文的变量对象构成的链表。每个作用域规定了如何查找变量,也就是确定当前执行代码对变量的访问权限从ES6 规范文档可以了解到,ES6 的执行上下文中有三种组件:词法环境(LexicalEnvironment
)、变量环境(VariableEnvironment
)、this 绑定(ThisBinding
)。这地方与 ES5 不太一样,为了区分 var
和 let
/const
声明的变量存储位置。
推荐阅读@阅文前端团队-夏佳昊翻译的理解 Javascript 执行上下文和执行栈,再阅读这篇文章会更容易理解。
在全局执行上下文中,this
的值指向全局对象,在浏览器中,this
的值指向 window
对象,node.js
中指向 global
对象。
在函数执行上下文中,this
的值取决于函数的调用方式。如果它被一个对象引用调用,那么 this
的值被设置为该对象,否则 this
的值被设置为全局对象或 undefined
(严格模式下)
A Lexical Environment is a specification type used to define the association of Identifiers to specific variables and functions based upon the lexical nesting structure of ECMAScript code. A Lexical Environment consists of an Environment Record and a possibly null reference to an outer Lexical Environment.
词法环境是一种规范类型,基于词法嵌套结构来定义标识符与特定变量和函数的关联关系。词法环境由两部分组成:
环境记录(environment record
):存储变量和函数声明的实际位置(变量对象)。环境记录也分为两类:
Function Environment
。Global Environment
。对外部环境的引用:表示它可以访问其外部词法环境,可以为 null
。(作用域链)
用伪代码的形式表示就如下:
ExecutionContext = {
ThisBinding: < Global Object >
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Object" / "Declarative",
// Identifier bindings go here
}
outer:
}
}
Identifies the Lexical Environment whose EnvironmentRecord holds bindings created by VariableStatements within this execution context.
变量环境是一种特殊的词法环境,它的环境记录维护了执行上下文中的 VariableStatements
创建的绑定。
在 ES6 中,LexicalEnvironment
组件和 VariableEnvironment
组件的区别在于前者用于存储函数声明和变量( let
和 const
)绑定,而后者仅用于存储变量( var
)绑定。
使用 var
关键字声明变量时,如var a = 'a'
当进入该执行上下文时,会在其执行上下文中的变量对象中声明该变量a: undefined
,即声明提升(hoist
),等解析到赋值语句时再更新这个变量的值
假如执行上下文是函数执行上下文,则这个变量是局部变量,如果是全局执行上下文,则其声明的是一个全局变量。
访问这些变量时,会严格按照作用域链访问
比如下面的例子,
c
是局部变量,当baz
的执行上下文弹出后,在全局执行上下文的变量对象中并没有声明c
,且作用域链未指向baz
的变量对象,所以报错c is not defined
function baz() {
console.log(c);
var c = 3;
}
baz(); // undefined
console.log(c); // c is not defined
如果未使用 var
关键字,则隐性声明一个全局对象属性,如b = 'b'
,则是在全局对象上增加一个属性b
,其值为'b'
全局执行上下文的变量对象寄宿在全局变量对象上,所以不使用 var
声明的对象在所有执行上下文中都可以访问
不会声明提升,遇到声明赋值语句时,直接在全局对象上添加该属性
比如下面的例子,未使用隐性声明变量
a
,不存在变量声明提升,所以执行到console.log(a)
语句时,foo
函数执行上下文中的激活对象中没有声明变量a
,所以报错a is not defined
function foo() {
console.log(a);
a = 1;
}
foo(); // a is not defined
全局对象在不同实现中表示也不同,比如浏览器端就是 window
、node.js
中就是 global
,全局对象是全局变量对象的宿主,所以在全局变量对象中可以访问全局对象中的属性
全局变量对象中的属性无法通过 delete
删除,而全局对象的属性可以通过 delete
删除。其实质是全局变量上的属性描述符 configurable
被设置为了 false
,所以无法删除、
ECMA-262 13.3.1 Let and Const Declarations:
let
andconst
declarations define variables that are scoped to the running execution context’s LexicalEnvironment. The variables are created when their containing Lexical Environment is instantiated but may not be accessed in any way until the variable’s LexicalBinding is evaluated. A variable defined by a LexicalBinding with an Initializer is assigned the value of its Initializer’s AssignmentExpression when the LexicalBinding is evaluated, not when the variable is created. If a LexicalBinding in alet
declaration does not have an Initializer the variable is assigned the value undefined when the LexicalBinding is evaluated.
上面的定义可以得到三个结论:
let
和 const
声明的是一个执行上下文中的块级词法环境的变量let
和 const
当进入词法环境中时就会创建这些变量,即存在声明提升,但在声明语句前不可使用该变量,即暂时性死区(temporal dead zone
, TDZ
)let
声明变量时未初始化值,则默认为 undefined
其他结论:
let
和 const
不可重复声明const
必须初始化值,且该变量(常量)具有不可变性。但其不可变指的是基础类型不可变,但引用类型的变量声明只是栈区中指向堆区的指针,所以可以修改引用类型在堆区的实际数据。let a = "a";
function foo(f) {
const b = "b";
debugger;
let d = "d";
}
foo("f");
var g = "g";
在进入 debugger
断点处时,执行全局执行上下文与函数执行上下文的可以表示为如下:
GlobalExecutionContext = {
ThisBinding: ,
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Object",
// Identifier bindings go here
a: 'a',
foo: < function >
}
outer:
},
VariableEnvironment: {
EnvironmentRecord: {
Type: "Object",
// Identifier bindings go here
g: undefined,
}
outer:
}
}
FunctionExecutionContext = {
ThisBinding: ,
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Declarative",
// Identifier bindings go here
Arguments: {
f: 'f',
length: 1
},
b: 'b',
d: < uninitialized >
},
outer:
}
}
在 chrome 控制台运行,断点如下: