思考题
开始这篇文章前,请先思考一下下面代码的输出分别是什么?
// example 1
function foo() {
console.log(a);
a = 1;
}
foo(); // ???
// example 2
function bar() {
b = 2;
}
bar();
console.log(b); // ???
// example 3
function baz() {
console.log(c);
var c = 3;
}
baz(); // ???
console.log(c); // ???
前置知识:执行上下文和变量对象
可以阅读@冴羽大神的博客JavaScript 深入之变量对象
执行上下文(
execution context
):当JavaScript
代码执行一段可执行代码(executable code
)时,其所创建的对应的执行环境。可执行代码包括全局代码
、函数代码
、eval 代码
,所以也分成全局执行上下文
、函数执行上下文
、eval 执行上下文
每个执行上下文中包含了三部分:
- 变量对象(
Variable Object, OV
):与执行上下文相关的数据作用域,存储了在上下文中定义的变量和函数声明。在函数执行上下文中又叫做激活对象(Activated Object, AO
)。 - 作用域链(
Scope Chain
):由多个执行上下文的变量对象构成的链表。每个作用域规定了如何查找变量,也就是确定当前执行代码对变量的访问权限 - this
var 关键字做了什么?
-
使用
var
关键字声明变量时,如var a = 'a'
当进入该执行上下文时,会在其执行上下文中的变量对象中声明该变量a: undefined
,即声明提升(hoist
),等解析到赋值语句时再更新这个变量的值 -
假如执行上下文是函数执行上下文,则这个变量是局部变量,如果是全局执行上下文,则其声明的是一个全局变量。
-
访问这些变量时,会严格按照作用域链访问
比如例子 3,
c
是局部变量,当baz
的执行上下文弹出后,在全局执行上下文的变量对象中并没有声明c
,且作用域链未指向baz
的变量对象,所以报错c is not defined
不使用 var 声明变量
-
如果未使用
var
关键字,则隐性声明一个全局对象属性,如b = 'b'
,则是在全局对象上增加一个属性b
,其值为'b'
-
全局执行上下文的变量对象寄宿在全局变量对象上,所以不使用
var
声明的对象在所有执行上下文中都可以访问 -
不会声明提升,遇到声明赋值语句时,直接在全局对象上添加该属性
比如例子 1 未使用隐性声明变量
a
,不存在变量声明提升,所以执行到console.log(a)
语句时,foo
函数执行上下文中的激活对象中没有声明变量a
,所以报错a is not defined
全局变量对象与全局对象
全局对象在不同实现中表示也不同,比如浏览器端就是 window
、node.js
中就是 global
,全局对象是全局变量对象的宿主,所以在全局变量对象中可以访问全局对象中的属性
全局变量对象中的属性无法通过 delete
删除,而全局对象的属性可以通过 delete
删除。其实质是全局变量上的属性描述符 configurable
被设置为了 false
,所以无法删除、
解答
- 例子 1 的输出是
a is not defined
- 例子 2 的输出是
2
- 例子 3 的输出是
undefined
和c is not defined