javascript作用域、作用域链
为了深度理解javascript作用域链的产生,本文会和预编译过程一起分析。(如果你并不知道预编译过程,请点击这里:js预编译)
执行期上下文:当函数执行时,会创建一个称为执行期上下文的内部对象(AO)。
函数每次执行时对应的上下文都是独一无二的,所以多次调用一个函数会导致创建多个执行期上下文。
当函数执行完毕,它所产生的执行期上下文被销毁。(简答说就是函数执行,创建AO,执行完,销毁AO,再执行函数,再创建新AO,执行完再销毁AO…)
[[scope]]:内部属性,指的就是我们所说的作用域,其中存储了执行期上下文的集合。(函数被创建的时候生成)
作用域链:[[scope]]中所存储的执行期上下文对象的集合,这个集合呈链式连接,我们把这种链式连接叫做作用域链。
看个小demo:
function Father() {
function Son() {
var er = 234;
console.log(ba);
}
var ba = 123;
return Son;
}
var global;
var newson = Father();
newson();
上面代码执行,控制台会打印123。
【问】函数执行完应该把变量销毁,为什么还能打印出变量ba呢?看完你就知道答案了。
下面开始分析代码运行过程:
首先,全局预编译生成GO(Global Object)对象
GO{
global : undefined,
newson : undefined,
Father : function Father() { ...函数体 }
}
此时,Father()函数被创建,Father()作用域链中插入GO对象。
程序运行,调用Father()函数(倒数第二行代码),预编译创建AO对象:
AO{
ba : undefined,
Son : function Son() { ...函数体 },
}
Father()函数执行,Father()作用域链中插入AO对象。
同时,Son()函数创建,Son()作用域链初始化(直接将Father()作用域链复制一份)
然后执行语句 ba = 123;AO里的ba变量被赋值,AO{ ba : 123,… }
Father()执行完毕,return Son。
同时销毁AO对象(其实是销毁指针,找不到了而已)
程序继续执行,执行newson();(最后一行代码)。newson只是函数Son()的别名,实际是执行函数Son();
Son()函数执行,创建AO对象,并插入到Son()函数作用域链最顶端。
执行语句 console.log(ba)
根据作用域链,自上而下找ba变量(第一个AO没有,第二个AO有),找到后打印输出: 123。
最后newson()函数执行完,销毁AO
上面程序运行过程中,虽然函数Father()执行期上下文已经被销毁,但是函数Son()被return出去了,依然可以通过作用域链访问变量ba,Son也被称为闭包。
【总结】函数创建的时候,会复制父级作用域链中已有对象。函数执行的时候,会创建AO对象,放在自己作用域链的顶端。函数执行完,会销毁自己的AO对象,从父级继承(复制)到作用域中的对象不销毁。
当内部函数在外部执行时,仍可以访问父级函数的内部变量,这就是闭包的概念。