今天学习了coderwhy老师的JS高级课程,对作用域有了更深的了解,也理解了常见的作用域面试题的解题思路,以下仅作记录。
题目:
var message = "Hello Global"
function foo() {
console.log(message)
}
function bar() {
var message = "Hello Bar"
foo()
}
bar()
首先全局对象Global Object会在编译阶段对所有定义的变量进行添加,则
{
window: Object,
xxx: ...
message: undefined,
foo: 0xa00,
bar: 0xb00
}
其中由于foo和bar为函数,全局环境记录的是它们的引用地址(这里用0xa00模拟),而函数自身的Activation Object也会在编译阶段添加函体定义的变量,同时会记录自己的父级作用域[parent scope](在这里是GO,如果函数里面嵌套函数的话,也是一层层的记录则形成作用域链,比如变量m如果在函数体AO内找不到,会到它的父级作用域[parent scope]寻找变量m,如果没有就一直找到全局对象为止)。
foo函数对象0xa00
[parent scope]:Global Object
函数体:代码块(函数体内没有定义变量,编译阶段无需初始化)
bar函数对象0xa00
[parent scope]:Global Object
函数体:代码块({message: undefined})
所有VO(包含GO和AO编译完成后),执行上下文调用栈(ECStack)开始从上到下按顺序执行代码
1.全局message赋值为Hello Global
2.调用bar函数(bar函数执行上下文(FEC)进入调用栈,直到函数执行完成就退出栈)
3.bar函数内message赋值为Hello Bar
4.调用foo函数(foo函数执行上下文(FEC)进入调用栈,直到函数执行完成就退出栈)
5.foo函数打印message变量
6.因为foo自身的AO无该变量,才到父级作用域[parent scope]寻找
7.它的父级作用域是GO,找到message变量,输出Hello Global(执行完毕,退出栈)
按照先编译后执行的顺序,即可完成题目,注意的是如函数体内var a = b = 100,如果b在函数体内无声明,会被添加到父级作用域的AO上,还有如果函数体内return语句后还有代码如var c = 10;那么它在编译阶段不会受影响还是会被添加到自身AO上,但该代码不会被执行即c为undefined不会赋值为10
总结(coderwhy老师原话,不是我总结的)
基于早期ECMA的版本规范:
每一个执行上下文会被关联到一个变量环境(variable object,VO),在源代码中的变量和函数声明会被作为属性添加到VO中。对于函数来说,参数也会被添加到VO中。
最新的ECMA的版本规范中:
每一个执行上下文会关联到一个变量环境(VariableEnvironment)中,在执行代码中变量和函数的声明会作为环境记录(Environment Record)添加到变量环境中。对于函数来说,参数也会被作为环境记录添加到变量环境中。