美文网首页
一篇就够-从底层理解闭包

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

作者: johe_jianshu | 来源:发表于2020-03-24 19:13 被阅读0次

闭包

理论中的闭包

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

自由变量:

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

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

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

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

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

实践中的闭包

  • 从理论角度,所有的函数都是闭包。因为它们在创建的时候就将上层上下文的数据保存起来了。(创建时的[[scope]]属性,将其上层上下文的作用域链保存下来)
  • 从实践角度,要满足两个条件的函数才算闭包
    • 即使创建它的上下文已经被销毁,它仍然能够访问父级上下文的变量对象
    • 在代码中引用了自由变量
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
    }
}

相关文章

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

    闭包 理论中的闭包 闭包是指那些能够访问自由变量的函数 自由变量: 自由变量是指在函数中使用的,但既不是函数参数,...

  • 「Python」闭包

    闭包的条件 闭包,从字面意思上可能不太好理解是什么意思,但是从闭包的条件入手会相对比较好理解。闭包需要满足三个条件...

  • 解释JavaScript中的闭包

    去年我写了一篇“closures的简介”,它的目的是帮助大家理解‘什么是闭包,闭包是如何工作的’。现在我尝试从另外...

  • JavaScript深入理解-闭包(Closure)

    推荐文章:学习Javascript闭包(Closure)- 阮一峰javascript深入理解-从作用域链理解闭包...

  • JavaScript闭包

    什么是闭包? 闭包(closure)我们从汉字的字面意思可以理解为:闭:封闭的 闭合 关闭包:包裹 ...

  • js作用域链

    之前写过一篇JavaScript 闭包究竟是什么的文章理解闭包,觉得写得很清晰,可以简单理解闭包产生原因,但看评论...

  • Gradle开发-Groovy闭包

    # 闭包 闭包的基础知识 闭包的使用 闭包 this,owner,delegate 的理解 总结 ## 闭包的基础...

  • Node异步

    理解闭包 从形式来看,闭包就是在函数里面定义一个函数,从特点来说,子函数能够读写父函数的局部变量。 闭包能够访问外...

  • Swift5 闭包及其应用

    关于如何理解闭包 学习闭包的第一个难点就是理解闭包,可能很多人用了很久的闭包都还不太清楚闭包到底是什么,我这里提供...

  • swfit autoclosure (闭包)

    闭包是什么很简单就是一段代码,同理lambdas ,底层是什么 目前理解就通C语言的函数指针吧,这个指针可以保存一...

网友评论

      本文标题:一篇就够-从底层理解闭包

      本文链接:https://www.haomeiwen.com/subject/rxeayhtx.html