引言
闭包作为JavaScript中的一个核心概念,常常在面试中被提及,但其确切定义和工作原理却鲜为人知。本文旨在通过对比MDN、Stack Overflow上的讨论以及《你不知道的JavaScript》书中的解释,深入剖析闭包的本质。我们将一起探索闭包如何与词法环境相互作用,以及它们在JavaScript V8引擎编译过程中的角色。通过具体代码示例和对词法环境结构的分析,我们将揭示闭包如何在JavaScript中实现,并讨论它们在现代编程实践中的重要性。
概念定义
MDN 定义
MDN 中对于闭包的定义如下:
函数与对其状态即词法环境(lexical environment)的引用共同构成闭包(closure)。也就是说,闭包可以让你从内部函数访问外部函数作用域。在 JavaScript,函数在每次创建时生成闭包。
令人诧异的是在我搜索关键字 "closure MDN" 还发现了 Stack Overflow 上面对于 MDN 上 closure 的争论。
你不知道的 js中的定义
你不知道的 js 上册中对于闭包也有如下的定义:
- 当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。
- 闭包是基于词法作用域书写代码时所产生的自然结果。 其还对闭包的行为结果进行了阐述: 这个函数在定义时的词法作用域以外的地方被调用。闭包使得函数可以继续访问定义时的词法作用域。
计算机科学中的定义
在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是在支持头等函数的编程语言中实现词法绑定的一种技术。闭包在实现上是一个结构体,它存储了一个函数(通常是其入口地址)和一个关联的环境(相当于一个符号查找表)。环境里是若干对符号和值的对应关系,它既要包括约束变量(该函数内部绑定的符号),也要包括自由变量(在函数外部定义但在函数内被引用),有些函数也可能没有自由变量。
定义里面涉及到的几个名词解释如下:
- 头等函数:是指在程序设计语言中,函数被当作头等公民。这意味着,函数可以作为别的函数的参数、函数的返回值(重点),赋值给变量或存储在数据结构中。
- 约束变量:简单理解为函数内部定义的变量。
- 自由变量:简单理解为在函数内部使用,在外部定义的变量。
根据上面的定义可以确定闭包的几个表现:
- 函数a里返回一个函数b;
- 函数b引用了外部变量(函数a里的变量);
wiki 里面给出的C语言闭包示例如下:
typedef int (^IntBlock)();
IntBlock downCounter(int start) {
__block int i = start;
return Block_copy( ^int() {
return i--;
});
}
IntBlock f = downCounter(5);
printf("%d", f());
printf("%d", f());
printf("%d", f());
Block_release(f);
lexical environment & lexical scope
v8 编译步骤
- 分词/词法分析(Tokenizing/Lexing)
- 解析/语法分析(Parsing)
- 生成可执行代码
在第一步中的词法分析,它会登记变量声明、函数声明、函数声明的形参, 后续代码执行的时候就知道去哪里拿变量的值和函数了, 这个登记的地方就是 Lexical Enviroment(词法环境)
Lexical environment 结构
词法环境是在代码定义的时候决定的,跟代码在哪里调用没有关系。所以说 JavaScript 采用的是词法作用域(静态作用域)。
Lexical environment 由 enviromentRecord 和 outer 两部分组成
environment = {
// storage
environmentRecord: {
type: "declarative",
// storage
},
// reference to the parent environment
outer: <...>
};
environmentRecord
environmentRecord 分为声明式环境记录和对象式环境记录
Declarative Environment Record 声明式环境记录
用来记录直接由标识符定义的元素, 比如变量、常量、let、class、module、import 以及函数声明
声明式环境记录又分为两类
- 函数环境记录(Function Environment Record):用于函数作用域。
- 模块环境记录(Module Environment Record):模块环境记录用于体现一个模块的外部作用域,即模块 export 所在环境。
Object Environment Record 对象式环境记录
主要用于 with 和 global 的词法环境
解释闭包
function foo() {
var a = 2;
function bar() {
console.log(a);
}
return bar;
}
var baz = foo();
baz(); // 2 —— 朋友,这就是闭包的效果
使用你不知道的js 中的说法, foo 的词法环境中定义了 a 和 函数 bar , bar 的词法环境的 outer 指向的是 foo , 所以能否访问到变量 a 。
foo 执行后返回了 bar 的引用并赋给了 baz , 而 baz 是定义在全局词法环境中, 其执行时 bar 的所处的词法环境不是 foo , 但由于其 outer 指向 foo 所以还是能访问 a 。
这种不在定义所处的词法环境中执行获取其定义所在的词法环境的方法就是闭包。
总结
闭包是一种技术,其实现总是一个结构体,这个结构体包含函数和一个环境(在JS中正常是词法环境Lexical environment)。其表现为能让外部环境能访问被return函数定义所在的词法环境。