闭包属于“函数式编程”的流派

  • 闭包是指有权访问另一个函数作用域中的变量的函数。——《JavaScript 高级程序设计》
    • 不管函数有没有导出 ```javascript function foo() { var n = 0; function bar(){ console.log(n); } bar(); }

foo();

  1. - 闭包允许函数访问并操作函数外部的变量。——《JavaScript 忍者秘籍》
  2. - 当函数可以记住并访问所有的记法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行<br />闭包使用函数可以继续访问定义时的词法作用域。——《你不知道的 JavaScript
  3. ```javascript
  4. function foo(){
  5. var n = 0;
  6. return function bar(){
  7. console.log(n);
  8. };
  9. }
  10. foo()();
  • 所有 JavaScript 函数都是闭包。——《JavaScript 权威指南》

    • 函数变量可以保存在函数作用域内
      function foo (){
      var n = 0;
      }
      

      闭包从哪里来?

  • 彼得 · 兰丁 在 1964 发表的一篇论文中 第一次定义了闭包

    • 闭包,包含了一个 λ 表达式和它所被计算所需的相关环境
  • Lisp1.5,束缚自由变量的幽灵 1962
    • 只出现闭包的雏形(一个局部的静态作用域)
      但由于 Lisp 是动态作用域的,要使用静态作用域要复杂地维护一个符号表,最终 Lisp 与闭包擦肩而过,没有出现闭包
  • Scheme 与闭包,Scheme 是静态作用域,出现闭包
  • JavaScript 与闭包
    • JavaScript 从 Scheme 借鉴了很多东西
      • 函数是一等公民
      • 静态作用域
    • JavaScript 在一定程序扩展闭包

      闭包是什么?

      λ 演算

      —— 变量 抽象 应用 自由变量 开放表达式

      λ 演算语法

  1. 变量 可以绑定值的东西
    • 数字形式 是一个符号 x
    • JavaScript 是一个标识符 x
    • λ 演算 是一个符号 x
    • 分为 约束变量(绑定了值的变量,已赋值的变量)
      自由变量(未绑定值的变量,未赋值的变量)
  2. 抽象 (定义函数) 输入一个值 ,输出这个值加 1 后的值
    • 数字形式 闭包 closure - 图1
    • JavaScript x => x + 1
    • λ 演算 闭包 closure - 图2
  3. 应用 (调用函数) 值传给函数并执行
    • 数字形式 闭包 closure - 图3
    • JavaScript (x => x + 1)(8) 使用 IIFE 来表示
    • λ 演算 闭包 closure - 图4

      开放表达式 与 闭合表达式

  • JS 的写法 (x, y) => x + y
    x => y => x + y 使用柯里化的写法
  • λ 演算写法

闭合表达式:所有的变量都是约束变量

  • JS 的写法 x => x + y
  • λ 演算写法 闭包 closure - 图5

开放表达式:包含自由变量

在开放表达式 中,如何找到 y 从而计算这个表达式的值呢?
假设程序没有任何错误,y 是实际存在的,只不过不在当前表达式中
这样有两种方法

  1. 暂不去计算表达式的值,先计算其它。
    当得到 y 的值时,再带回来计算。
    即 y 的值是在运行时获取到

    有些 Lisp 使用符号表来实现,但是符号表的维护成本很高

  2. 预先定义一个环境,使用得表达式中每一个自由变量都得到一个绑定值
    开放表达式 变成了一个 闭合表达式
    构建了一个 闭包

    彼得 · 兰丁首次定义闭包:
    闭包,包含了一个 λ 表达式(匿名函数)和它所被计算所需的相关环境

推导出最原始闭包的概念

  • 闭包 = 开放 λ 表达式 + 使得开放表达式的闭合的一个环境
  • 闭包 = 函数 + 使得函数中每一个自由变量都获得绑定值的一个环境 :::info 闭包 = 函数 + 环境
    函数是包含自由变量的函数
    环境是使所有自由变量都获得绑定值的环境 :::

    JavaScript 中的闭包

    :::info

    MDN 的定义

    A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In other words, a closure gives you access to an outer function’s scope from an inner function. In JavaScript, closures are created every time a function is created, at function creation time.

一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。 :::

  1. 函数和对其周围状态(词法环境)的引用捆绑在一起构成闭包
    • 周围状态 [[Environment]] -> 词法环境
      • 每个函数都有
      • 存放 词法环境 的引用
      • V8 引擎中无法读写
    • ECMA262 定义
      • 使得函数“关闭”的词法环境
        在函数代码 运行时作为外部环境来使用
      • 词法环境对象
        • 包括
          • 整个脚本文件执行前会生成一个
          • 函数实例创建后会生成一个
          • eval
          • with
          • catch
        • 作用
          • 当前环境记录
          • 对外部词法环境的引用 (近似作用域链)
  2. 也就是说,闭包可以让你从内部函数访问外部函数作用域
    1. 就是闭包的作用
    2. 也就是作用域链
  3. 在 JavaScript 中,每当函数被创建时 ,就会在函数生成时生成闭包

    1. 所有函数都能访问到 window 外部作用域的值

      闭包往何处去?

      闭包是为解决函数参数问题 Function Arguments Problem

      为何会出现这个问题?

      出现这个问题会有
  4. 函数嵌套定义并作为返回值

  5. 函数执行使用了调用栈来实现
  6. 内部函数使用了外部函数的变量

针对这三点,只要解决其中一点就能解决这个问题

  1. 不让函数为一等公民
  2. 使用调用堆可以解决

但使用堆的成本和解析器的设计复杂程序大大增加

  1. 使用闭包 ECMAScript 使用这种解决方法
    在函数内部有一个周围状态,引用着词法环境对象 ,记录外部词法环境的引用(作用域链)