要想清楚的知道作用域、作用域链是什么,闭包是如何产生的,我们首先要了解JavaScript基本编译过程。
JS运行三部曲
一、语法分析
对于JavaScript这种解释性语言来说,通过词法分析 -> 语法分析 -> 语法树,就可以开始解释执行了。其具体过程是这样子的: 1、词法分析是将字符流(char stream)转换为记号流(token stream) 2、语法分析成 AST (Abstract Syntax Tree),你可以在这里试试 esprima.org/
不过了解这些对于我们理解作用域和闭包并没有什么实际用处,我们只需要知道语法分析过程就是保证你写的代码没有常规性错误,也就是不报错。真正需要理解的是预编译阶段。
二、预编译
预编译阶段只需要记住四点(四部曲):
1、创建AO对象
2、找形参和变量声明,将变量和形参名作为AO属性名,值为undefined
3、将实参值和形参统一
4、在函数体里面找函数声明,值赋予函数体
解释:AO(Activation Object)也就是我们平常说的执行期上下文:当函数执行的前一刻,会创建一个称为执行期上下文的内部对象,一个执行期上下文定义了一个函数执行时的执行环境,函数每次执行时对应的执行期上下文都是独一无二的,多次调用一个函数会创建多个执行期上下文,当函数执行完毕,它所产生的执行期上下文也就被销毁了(用完就扔跟我们平常说的渣男很是相像啊)。 其中最大的AO我们称之为GO(Global Object)全局对象,在浏览器中也就是window了。 举个栗子:
var xx = 1;
function a(aa){
var cc = 'a'
function b(){
var bb = 'b'
}
b()
}
a(11)
复制代码
1、创建AO对象(此时全局模式下创建的是GO)
GO => {
this:window,
window: Object
}
2、找形参和变量声明,将变量和形参名作为AO属性名,值为undefined,此时没有形参,只有一个‘xx’变量声明
GO => {
this:window,
window: Object,
xx: undefined
}
3、将实参值和形参统一(暂无)
4、在函数体里面找函数声明,值赋予函数体,此时函数声明有一个a
GO => {
this:window,
window: Object,
xx: undefined,
a: fn()
}
这个过程中如果有重复的属性名,后面的覆盖前面的
复制代码
三、解释执行
解释执行阶段就是解释一行执行一行,碰到函数调用就创建一个新的AO。接上面的例子:
执行 xx = 1 此时
GO => {
this:window,
window: Object,
xx: 1,
a: fn
}
执行a函数:重复四部曲
1、a函数执行创建AO:aAO => {
context: GO // 存放他出生时的执行期上下文
}
2、找形参和变量声明,将变量和形参名作为AO属性名,值为undefined,此时有形参aa,变量声明cc
aAO => {
aa:undefined,
cc: undefined,
context: GO
}
3、将实参值和形参统一:
aAO => {
aa:11,
cc: undefined,
context: GO
}
4、在函数体里面找函数声明,值赋予函数体,此时函数声明有一个b
aAO => {
aa:11,
cc: undefined,
b:fn,
context: GO
}
然后就又是解释执行
cc = 'a' 此时:
aAO => {
aa:11,
cc: 'a',
b:fn,
context: GO
}
执行b函数:重复四部曲
复制代码
最终我们看看b的 AO:
bAO => {
bb: 'b',
context: aAo => {
aAO => {
aa:11,
cc: 'a',
b:fn,
context: GO => {
this:window,
window: Object,
xx: 1,
a: fn
}
}
}
}
复制代码
至此我们我们再回头想想我们文章开头提到的什么是作用域、作用域链?是不是好像明白了什么? 其实我们看到的bAo就是一个作用域链了,b函数的作用域内只有一个bb变量,当它需要获取参数xx时,首先会在他本身的作用域查找,没找到就顺着他的执行期上下文向下查找,一直到找到或者到达GO,这种把作用域连起来的方式就是作用域链了。
对于闭包
闭包就是能够读取其他函数内部变量的函数 -- 百度百科
举个例子:
function a(aa){
var cc = 'a'
return function(){
var bb = 'b'
console.log(cc)
}
}
var fn = a(11)
复制代码
a函数执行完了之后它自己的执行去上下文销毁,但是返回了一个函数保存在全局的fn变量上,这个fn的的作用域上还保存着a函数的执行期上下文,就形成了闭包。
一个不算总结的总结
第一次发文,错误地方请各位看官直接指出,叙述不清的地方也请各位大佬斧正。
建议:关于作用域、作用域链的面试题,如果有同学搞不明白的,多写几次四部曲