一篇就够-从底层理解闭包

2020-03-24  本文已影响0人  johe_jianshu

闭包

理论中的闭包

闭包是指那些能够访问自由变量的函数

自由变量:

自由变量是指在函数中使用的,但既不是函数参数,也不是函数的局部变量的变量(不存在于当前函数执行上下文中的变量对象中的变量)

即闭包由=函数+函数能够访问的自由变量

var a = 1;
function foo(){
    console.log(a)
}
foo()

foo函数可以访问变量a,但是a既不是foo函数的局部变量,也不是foo函数的参数,所以a就是自由变量。
所以foo + foo函数访问的自由变量a就构成了闭包。

理论上,从技术的角度来说,所有的函数都是闭包

实践中的闭包

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

var foo = checkscope();
foo();

从分析以上代码的执行上下文栈变化情况,我们就可以知道为什么checkScope执行上下文即使被摧毁了,但f函数仍然能够访问到。

  1. 初始化全局上下文
globalContext = {
    VO:[global,scope,foo],
    Scope:[globalContext.VO],
    this:globalContext.VO
}
  1. 推入全局上下文到执行上下文栈
ECStack = [ globalContext ]
  1. checkscope函数被创建,[[scope]]属性被赋予为父级上下文中的作用于链
checkscope.[[scope]] = [...globalContext.Scope]
  1. checkscope被执行,初始化checkscope的变量对象AO,初始化checkscope的上下文,将[[scope]]赋值给作用域链,然后推入AO,再推入checkscope的上下文到执行上下文栈(这里省略了函数的分析阶段)
checkscopeContext = {
    AO:{
        arguments:{
            length:0
        },
        scope:'local scope',
        f: reference of f(){}
    },
    Scope:[AO,...[[scope]]]
}
  1. f函数被创建,设置其[[scope]]为父级上下文的作用域链(当前父级为checkscopeContext)
f.[[scope]] = [...checkscope.Scope]
  1. checkscope执行完毕,弹出checkscope上下文
ECStack.pop()
  1. f函数被执行,推入f函数的执行上下文,并将f函数的执行上下文初始化
fContext = {
    AO:{
        arguments:{
            length:0
        }
    },
    Scope:[AO,...f.[[scope]]]
}
  1. 根据f的作用于链Scope查找scope变量(存在于checkscopeContext.AO中),即使checkscopeContext已经被销毁,但是fContext.Scope中仍然保存着。

  2. f函数执行结束,将其上下文从执行上下文栈中弹出

checkscopeContext即使被摧毁了,f函数依然可以读取到checkScopeContext.AO,这是因为f函数在创建时,它的[[scope]]属性就保存了父级的作用域链,所以f函数可以通过作用域链找到它

面试题分析

var data = [];

for (var i = 0; i < 3; i++) {
  data[i] = function () {
    console.log(i);
  };
}

data[0]();
data[1]();
data[2]();

答案都是3

在指向data0之前,全局上下文的VO为

globalContext = {
    VO:{
        data:[...],
        i:3
    }
}

闭包的形式:

var data = [];

for (var i = 0; i < 3; i++) {
  data[i] = (function closure(i) {
        return function(){
            console.log(i);
        }
  })(i);
}

data[0]();
data[1]();
data[2]();

输出0 1 2
这是由于创建data[i]函数的父级函数上下文在创建完函数后被销毁,其对象变量的i作为形参,在初始化时值已经固定。

data[0]Context = {
    AO:{
        arguments:{
            length:0
        }
    },
    Scope:[AO,closure[0]Context.AO,globalContext.VO]
}

在执行data[i]时,会访问对应的closure[i]Context.AO,所以是三个不同的AO对象

closure[0]Context = {
    AO:{
        arguments:{
            0:0
            length:1
        },
        i:0
    }
}
上一篇 下一篇

猜你喜欢

热点阅读