每天对自己多问几个为什么,总是有着想象不到的收获。 一个菜鸟小白的成长之路(copyer)
第二集:图解面试作用域面试题
面试题:
var message = 'copyer'
function foo() {
console.log(message); // copyer
}
function bar() {
var message = 'james'
foo()
}
bar()
在上面的面试题中,答案是 copyer
。为什么?
这里就涉及到了作用域链
的问题。废话不多说,直接画图。
前面我们知道,在执行代码之前,会被代码进行词法进行解析,并添加在全局的对象中。
所有的代码,都是在JS引擎中的调用栈
中执行,也就是执行上下文栈
在执行代码的前面,会进行词法分析,然后绑定在全局对象(GO
)中
var GlobalObject = {
window: GlobalObject,
message: undefined,
foo: 0x100,
bar: 0x200
}
所以在分析过程中,就会在堆中创建三个内存地址,存放三个对象。
值得注意的是,这里 [parent scope]
是在编译的时候,就已经确定父级作用域
了
词法分析完成后,就开始执行代码,然后给GO
里面的变量进行赋值
var GlobalObject = {
window: GlobalObject,
message: 'copyer',
foo: 0x100,
bar: 0x200
}
然后就是执行 bar()
,调用bar这个函数,这里也会创建函数执行上下文(FEC)
这里也会 创建一个对象 Activation Object(AO)
,保存预编译的变量和函数,如果有 函数嵌套就是AO
对象中。
然后就是开始执行代码、
var ActivationObject = {
message: 'james'
}
foo()
这里执行 foo
函数吗,也会在调用栈中创建一个 函数执行上下文
,以及在堆中重新创建一个AO
对象。这里就不画了。就看看完成的图吧。
上面就是整体的流程图,执行函数 进栈的操作
,执行完函数后,出栈的操作
这下在回头看面试题,在foo
函数中并没有message
这个变量,(即AO
对象中,没有message属性),那么按照作用域链继续查找, 根据scopechain
,AO
中没有,就去 parentScope
里面去找
而这里的 parentScope
就是 GO了,这里是在最初的编译的时候,就已经确定了。
所以 GO中的message为copyer
, 这下就知道为什么了吧。
目标:
理解 parentScope
的指向
理解 scopechain
的整体线