scope,closure,context

85 阅读3分钟

作用域

我的理解就是变量的可访问范围,按照MDN的定义:当前的执行上下文。

作用域有三种 全局作用域,函数作用域,块状作用域(ES6后)const,let+{},单纯的{}没有效果。

js中面对代码采用的是词法环境,也就是静态作用域。代码在编写的时候就已经写好作用域了。 与什么环境没有关系。

作用域链

通过作用域链使得位于执行上下文栈的顶部执行上下文和底部上下文的作用域产生联系。顶部上下文AO得以访问底部上下文变量对象VO。这样由多个执行上下文的变量对象构成的链表就叫做作用域链。

执行上下文

是一个js代码的运行环境。有三种全局执行上下文,函数执行上下文,eval执行上下文。 每一个执行上下文都有这三个重要的属性
1.变量对象。VO
2.作用域链 scopes chain
3.this

当把一个执行上下文压入栈,会发生这样的事情:
1.保存作用域链到 内部属性[[scope]]
2.用argument创建活动对象
3.初始化活动对象,加形参,函数声明,变量声明
4.将活动对象压入作用域链顶端
5.开始执行代码,改变AO的值

闭包

闭包按照MDN的解释是函数+自由变量,自由变量我理解就是作用域链上的变量。
产生的条件是返回函数,这个函数内部有对外部变量的引用。
是为了解决父作用域先于子作用域毁灭的问题。js可以返回函数,所以当你调用这个函数的时候,这个函数需要访问父作用域链上的内容,可是父作用域已经销毁了。难道为了这个函数父作用域不销毁吗,不可能,太占空间了。给函数加一个属性[[Scopes]],这个属性保存那些外部引用的变量。这个过程是在父函数初始化的过程。
一个例子:

var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f();
}
checkscope();

1.执行全局代码,创建全局上下文,全局上下文被压入执行上下文栈: ECStack = [ globalContext ];
2.初始化全局上下文。

    globalContext = {
        VO: [global],
        Scope: [globalContext.VO],
        this: globalContext.VO
    }

3.checkscope()函数也被创建,函数的[scope]属性保存globalContext.VO
4.全局运行代码,checkscope()函数执行,创建checkscope()函数上下文。
5.初始化checkscope()函数上下文.同时 f 函数被创建,保存作用域链到 f 函数的内部属性[[scope]]
1)复制[[scope]]属性给作用域链上
2)利用argument对象创建AO
3)初始化活动对象,即加入形参、函数声明、变量声明,
4)将AO压入作用域链的顶端

checkscopeContext = {
        AO: {
            arguments: {
                length: 0
            },
            scope: undefined,
            f: reference to function f(){}
        },
        Scope: [AO, globalContext.VO],
        this: undefined
    }

6.执行f()
等等,下一步可以自己分析。

返回函数在声明的时候(没有被调用的时候)就有一个属性[scope]来保存父函数的VO。在return 的时候,才会进行闭包处理,如果没有return 函数的话,就不需要闭包。这个处理会根据函数内部的引用情况过滤掉作用域链中多余的部分。

闭包是返回函数的时候扫描函数内的标识符引用,把用到的本作用域的变量打成 Closure 包,放到 [[Scopes]] 里。
闭包是在函数创建的时候,让函数打包带走的根据函数内的外部引用来过滤作用域链剩下的链。它是在函数创建的时候生成的作用域链的子集,是打包的外部环境。 eval函数由于是动态的,不知道它需要外部函数的什么内容,所以会把整个作用域都打包。所以很耗空间,所以别用eval()。
缺点: 内存泄漏,占空间,比如指引用obj.a,但是obj都不会销毁