作用域、闭包

79 阅读2分钟

[[scope]]

每个函数都是一个对象,其中有些属性是我们不可以访问,仅供js引擎存取的,[[scope]] 就是其中之一。[[scope]] 就是作用域,里面存储了执行期上下文对象的集合。这些集合呈链式链接,叫做作用域链

看看下面这段代码:

function a(){
    function b() {
        var b = 234
    }
    var a = 123
    b()
}
var glob = 100
a()

首先,a被定义的时候,a的scope属性指向的ScopeChain对象中的第0位会放GO

graph TD
a的ScopeChain --> 第0位
第0位 --> GlobalObject

然后a执行的前一刻,发生如下变化

graph TD
a的ScopeChain --> 第0位
a的ScopeChain --> 第1位
第0位 --> ActivationObject
第1位 --> GlobalObject

接下来b被定义时,b的scope第0位指向a的AO,第1位指向GO

graph TD
b的ScopeChain --> 第0位
b的ScopeChain --> 第1位
第0位 --> ActivationObject/a
第1位 --> GlobalObject

b被执行,再生成自己的AO,放到第0位

graph TD
b的ScopeChain --> 第0位
b的ScopeChain --> 第1位
b的ScopeChain --> 第2位
第0位 --> ActivationObject/b
第1位 --> ActivationObject/a
第2位 --> GlobalObject

b执行完,会断掉自己的AO链条,如果重新被执行,会再生成一个全新的AO,而不是原来的那一个。

我们在函数中查找变量时,会自顶向下查找作用域链。

闭包

当内部函数被保存到外部时,会生成闭包。缺点:闭包会导致原有作用域链不释放,造成内存泄漏。

function test(){
    var arr = []
    for(var i = 0; i < 10; i++){
        arr[i] = function() {
            console.log(i+'')
        }
    }
    return arr;
}
var myArr = test()
for (var j = 0; j <10;j++){
    myArr[j]() 
}//打印10个10

函数体被保存到了外部,形成了闭包,10个函数体的AO都用的同一个。(这时 i 已经变成了10)

可以通过立即执行函数来形成独一无二的执行期上下文,来达到输出0到9的效果⬇️

function test(){
    var arr = []
    for(var i = 0; i < 10; i++){
        (function (j) {
            arr[j] = function() {
                console.log(j+'')
            }
         }(i))//每次执行后被销毁,然后再创建一个全新的立即执行函数,因此每个立即执行函数都有自己独立的AO
    }
    return arr;
}
var myArr = test()
for (var j = 0; j <10;j++){
    myArr[j]() 
}//0,1,2,3,4,5,6,7,8,9

立即执行函数

针对初始化功能的函数。执行完就会销毁。

var num = (function cal(a,b){
    return (a+b)
}(1,2))

cal //执行完就找不到这个函数了,会报错。函数名可写可不写。
num //3  可以有返回值。

有两种写法:

(function (){}()) //W3C建议这种写法。会先识别最外层的括号,把里面的变成表达式,再用括号执行。

(function (){}) ()

只有表达式才能被执行,被括号括起来就是表达式了(或者赋值给变量、或者前面加个'+'、'!'等等),后面加个()(执行符号)就被执行了。