从Javascript编译过程谈谈什么是作用域、作用域链和闭包形成过程

要想清楚的知道作用域、作用域链是什么,闭包是如何产生的,我们首先要了解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函数的执行期上下文,就形成了闭包。

一个不算总结的总结

第一次发文,错误地方请各位看官直接指出,叙述不清的地方也请各位大佬斧正。

建议:关于作用域、作用域链的面试题,如果有同学搞不明白的,多写几次四部曲

转载于:https://juejin.im/post/5c52a44a6fb9a049f7469ece

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值