闭包属于“函数式编程”的流派
- 闭包是指有权访问另一个函数作用域中的变量的函数。——《JavaScript 高级程序设计》
- 不管函数有没有导出 ```javascript function foo() { var n = 0; function bar(){ console.log(n); } bar(); }
foo();
- 闭包允许函数访问并操作函数外部的变量。——《JavaScript 忍者秘籍》
- 当函数可以记住并访问所有的记法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行<br />闭包使用函数可以继续访问定义时的词法作用域。——《你不知道的 JavaScript》
```javascript
function foo(){
var n = 0;
return function bar(){
console.log(n);
};
}
foo()();
所有 JavaScript 函数都是闭包。——《JavaScript 权威指南》
彼得 · 兰丁 在 1964 发表的一篇论文中 第一次定义了闭包
- 闭包,包含了一个 λ 表达式和它所被计算所需的相关环境
- Lisp1.5,束缚自由变量的幽灵 1962
- 只出现闭包的雏形(一个局部的静态作用域)
但由于 Lisp 是动态作用域的,要使用静态作用域要复杂地维护一个符号表,最终 Lisp 与闭包擦肩而过,没有出现闭包
- 只出现闭包的雏形(一个局部的静态作用域)
- Scheme 与闭包,Scheme 是静态作用域,出现闭包
- JavaScript 与闭包
- 变量 可以绑定值的东西
- 数字形式 是一个符号 x
- JavaScript 是一个标识符 x
- λ 演算 是一个符号 x
- 分为 约束变量(绑定了值的变量,已赋值的变量)
自由变量(未绑定值的变量,未赋值的变量)
- 抽象 (定义函数) 输入一个值 ,输出这个值加 1 后的值
- 数字形式
- JavaScript
x => x + 1
- λ 演算
- 应用 (调用函数) 值传给函数并执行
- JS 的写法
(x, y) => x + y
x => y => x + y
使用柯里化的写法 - λ 演算写法
闭合表达式:所有的变量都是约束变量
- JS 的写法
x => x + y
- λ 演算写法
开放表达式:包含自由变量
在开放表达式 中,如何找到 y 从而计算这个表达式的值呢?
假设程序没有任何错误,y 是实际存在的,只不过不在当前表达式中
这样有两种方法
暂不去计算表达式的值,先计算其它。
当得到 y 的值时,再带回来计算。
即 y 的值是在运行时获取到有些 Lisp 使用符号表来实现,但是符号表的维护成本很高
预先定义一个环境,使用得表达式中每一个自由变量都得到一个绑定值
开放表达式 变成了一个 闭合表达式
构建了一个 闭包彼得 · 兰丁首次定义闭包:
闭包,包含了一个 λ 表达式(匿名函数)和它所被计算所需的相关环境
推导出最原始闭包的概念
- 闭包 = 开放 λ 表达式 + 使得开放表达式的闭合的一个环境
- 闭包 = 函数 + 使得函数中每一个自由变量都获得绑定值的一个环境
:::info
闭包 = 函数 + 环境
函数是包含自由变量的函数
环境是使所有自由变量都获得绑定值的环境 :::JavaScript 中的闭包
:::infoMDN 的定义
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 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。 :::
- 函数和对其周围状态(词法环境)的引用捆绑在一起构成闭包
- 周围状态 [[Environment]] -> 词法环境
- 每个函数都有
- 存放 词法环境 的引用
- V8 引擎中无法读写
- ECMA262 定义
- 使得函数“关闭”的词法环境
在函数代码 运行时作为外部环境来使用 - 词法环境对象
- 包括
- 整个脚本文件执行前会生成一个
- 函数实例创建后会生成一个
- eval
- with
- catch
- 作用
- 当前环境记录
- 对外部词法环境的引用 (近似作用域链)
- 包括
- 使得函数“关闭”的词法环境
- 周围状态 [[Environment]] -> 词法环境
- 也就是说,闭包可以让你从内部函数访问外部函数作用域
- 就是闭包的作用
- 也就是作用域链
在 JavaScript 中,每当函数被创建时 ,就会在函数生成时生成闭包
函数嵌套定义并作为返回值
- 函数执行使用了调用栈来实现
- 内部函数使用了外部函数的变量
针对这三点,只要解决其中一点就能解决这个问题
- 不让函数为一等公民
- 使用调用堆可以解决
但使用堆的成本和解析器的设计复杂程序大大增加
- 使用闭包 ECMAScript 使用这种解决方法
在函数内部有一个周围状态,引用着词法环境对象 ,记录外部词法环境的引用(作用域链)