JavaScript 闭包详解:从概念到 ECMAScript 实现机制
本文深入解析 JavaScript 闭包的核心概念、ECMAScript 实现机制及常见应用场景。通过五个典型代码示例,阐明局部变量在函数返回后的存活原理,并介绍执行上下文、词法环境等底层机制,最后展示闭包在单例模式中的实际应用。
JavaScript 闭包概述
前言:本文旨在介绍 JavaScript 的基础知识。JavaScript 拥有几个至关重要的语言特性,包括对象、原型继承以及闭包。其中,闭包对于习惯使用 C/C++ 等传统静态语言的开发者而言,是一种全新的语言特性。本文将通过具体示例来解析 JavaScript 闭包的特性,并结合 ECMAScript 语言规范,帮助读者更深入地理解闭包。
注:本文为入门级文章,示例素材整理自网络。如果您是资深开发者,欢迎提出技术性建议。本文仅讨论 JavaScript,不涉及其他语言对比。如果您对 JavaScript 感到不适,请自行跳过。
闭包的定义与本质
什么是闭包? 闭包(Closure)是静态语言所不具备的新特性。但它并非难以理解,简而言之,闭包可以定义为:
闭包是函数的局部变量集合,即便函数返回后,这些局部变量依然存在。 另一种理解方式是,函数返回后其“堆栈”并不释放,即这些函数堆栈并非在栈上分配,而是在堆上分配。 当在一个函数内部定义另一个函数时,就会产生闭包。 上述第二个定义是对第一个定义的补充说明。提取第一个定义的主谓宾——闭包是函数的“局部变量”集合。关键在于,这些局部变量在函数返回后仍可被访问。(此非官方定义,但有助于理解)
作为局部变量,它们可以被函数内的代码访问,这与静态语言无异。闭包的区别在于,局部变量在函数执行结束后,仍能被函数外的代码访问。这意味着函数必须返回一个指向闭包的“引用”,或将该“引用”赋值给外部变量,才能确保闭包中的局部变量被外部代码访问。当然,包含该引用的实体必须是一个对象,因为在 JavaScript 中,除基本类型外均为对象。遗憾的是,ECMAScript 未提供直接访问闭包局部变量的成员或方法。但在 ECMAScript 中,函数对象内定义的内部函数(inner function)可直接访问外部函数的局部变量。通过此机制,我们可以以下方式访问闭包。
代码如下:
function greeting(name) {
var text = 'Hello ' + name; // local variable
// 每次调用时,产生闭包,并返回内部函数对象给调用者
return function() { alert(text); }
}
var sayHello=greeting("Closure");
sayHello() // 通过闭包访问到了局部变量text
上述代码的执行结果是:Hello Closure。因为 sayHello() 函数在 greeting 函数执行完毕后,仍能访问定义在其内部的局部变量 text。
这就是传说中的闭包效果。闭包在 JavaScript 中有多种应用场景和模式,例如 Singleton(单例)、Power Constructor 等 JavaScript 模式都离不开对闭包的使用。
ECMAScript 闭包实现模型
ECMAScript 闭包模型 ECMAScript 如何实现闭包?希望深入了解的读者可查阅 ECMAScript 规范,此处仅作简要讲解,内容源自网络。
在 ECMAScript 脚本的函数运行时,每个函数都关联一个执行上下文(Execution Context)。该执行上下文包含三个部分:
文法环境(The LexicalEnvironment) 变量环境(The VariableEnvironment) this 绑定 其中第三点 this 绑定与闭包无关,本文不讨论。文法环境用于解析函数执行过程中使用的变量标识符。可将文法环境想象为一个对象,包含两个重要组件:环境记录(Environment Record)和外部引用(指针)。环境记录包含函数内部声明的局部变量和参数变量,外部引用指向外部函数对象的上下文执行场景。全局上下文场景中此引用值为 NULL。这种数据结构构成单向链表,每个引用指向外层上下文场景。
例如上文示例的闭包模型如下:sayHello 函数在最下层,上层是函数 greeting,最外层是全局场景。如下图所示:因此当 sayHello 被调用时,它会通过上下文场景找到局部变量 text 的值,从而在屏幕对话框中显示“Hello Closure”。变量环境(The VariableEnvironment)与文法环境作用基本相似,具体区别请参阅 ECMAScript 规范文档。
闭包典型示例分析
闭包样例 前文已大致了解 JavaScript 闭包的概念及其实现原理。下面通过五个示例深入理解闭包,示例源自 JavaScript Closures For Dummies(镜像)。 例子 1:闭包中局部变量是引用而非拷贝
代码如下:
function say667() {
// Local variable that ends up within closure
var num = 666;
var sayAlert = function() { alert(num); }
num++;
return sayAlert;
}
var sayAlert = say667();
sayAlert()
因此执行结果应该弹出 667 而非 666。
例子 2:多个函数绑定同一个闭包,因为它们定义在同一个函数内。
代码如下:
function setupSomeGlobals() {
// Local variable that ends up within closure
var num = 666;
// Store some references to functions as global variables
gAlertNumber = function() { alert(num); }
gIncreaseNumber = function() { num++; }
gSetNumber = function(x) { num = x; }
}
setupSomeGlobals(); // 为三个全局变量赋值
gAlertNumber(); //666
gIncreaseNumber();
gAlertNumber(); // 667
gSetNumber(12);//
gAlertNumber();//12
例子 3:当在一个循环中赋值函数时,这些函数将绑定同样的闭包
代码如下:
function buildList(list) {
var result = [];
for (var i = 0; i < list.length; i++) {
var item = 'item' + list[i];
result.push( function() {alert(item + ' ' + list[i])} );
}
return result;
}
function testList() {
var fnlist = buildList([1,2,3]);
// using j only to help prevent confusion - could use i
for (var j = 0; j < fnlist.length; j++) {
fnlist[j]();
}
}
testList 的执行结果是弹出 item3 undefined 窗口三次,因为这三个函数绑定了同一个闭包,而且 item 的值为最后计算的结果,但是当 i 跳出循环时 i 值为 4,所以 list[4] 的结果为 undefined。
例子 4:外部函数所有局部变量都在闭包内,即使这个变量声明在内部函数定义之后。
代码如下:
function sayAlice() {
var sayAlert = function() { alert(alice); }
// Local variable that ends up within closure
var alice = 'Hello Alice';
return sayAlert;
}
var helloAlice=sayAlice();
helloAlice();
执行结果是弹出“Hello Alice”的窗口。即使局部变量声明在函数 sayAlert 之后,局部变量仍然可以被访问到。
例子 5:每次函数调用的时候创建一个新的闭包
代码如下:
function newClosure(someNum, someRef) {
// Local variables that end up within closure
var num = someNum;
var anArray = [1,2,3];
var ref = someRef;
return function(x) {
num += x;
anArray.push(num);
alert('num: ' + num +
'\nanArray ' + anArray.toString() +
'\nref.someVar ' + ref.someVar);
}
}
closure1=newClosure(40,{someVar:'closure 1'});
closure2=newClosure(1000,{someVar:'closure 2'});
closure1(5); // num:45 anArray[1,2,3,45] ref:'someVar closure1'
closure2(-10);// num:990 anArray[1,2,3,990] ref:'someVar closure2'
闭包在实际开发中的应用
闭包的应用 Singleton 单件:
代码如下:
var singleton = function () {
var privateVariable;
function privateFunction(x) {
...privateVariable...
}
return {
firstMethod: function (a, b) {
...privateVariable...
},
secondMethod: function (c) {
...privateFunction()...
}
};
}();
这个单件通过闭包来实现。通过闭包完成了私有的成员和方法的封装。匿名主函数返回一个对象。对象包含了两个方法,方法 1 可以访问私有变量,方法 2 访问内部私有函数。需要注意的地方是匿名主函数结束的地方的 '()',如果没有这个 '()' 就不能产生单件。因为匿名函数只能返回了唯一的对象,而且不能被其他地方调用。这个就是利用闭包产生单件的方法。
JS动态构建复杂嵌套JSON对象的方法
2026-11-05 15:41:52
JS负数取余为什么是负数?详解余数与模运算区别
2026-11-05 13:34:41
JS 解压出现乱码或报错怎么解决?
2026-11-05 12:32:38
JS 中 startsWith、indexOf 与正则判断字符串前缀的对比
2026-11-04 22:19:43
JS解析Excel后怎么将数据转换为JSON格式
2026-11-04 20:58:28
