📖前言
在阅读了由KYLE SIMPSON创作的《你不知道的JavaScript上卷》的闭包部分后,我深刻地认识到了闭包的概念,在此书写一篇帮助理解闭包的文章,其中凝括了这本书里对闭包的知识讲解和结合deepseek的一些思考,相信你看完后,一定也能对闭包很熟悉!
🔍一个基础例子
下面我们来看一段代码
function foo(){
var a = 2;
function bar(){
console.log(a);
}
return bar;
}
var a = 3;
var baz = foo()
baz(); //2
foo函数的返回值是bar函数,赋值给了baz,所以baz也就是bar函数,当baz(bar)调用时,输出了函数里定义的var a = 2。想想这是为什么?
bar的词法作用域是foo函数的作用域,可是你会发现咱们执行bar(baz就是bar)时作用域是全局的作用域呢!但这时,bar输出的还是词法作用域,也就是foo函数作用域里面的变量a!想想这是为什么?
其实这些都是闭包的效果!
🧩闭包到底是什么
一句话概括:闭包是函数和其词法环境的结合,既包含结构(函数+变量),也包含行为(维持变量的引用),它是JavaScript的核心机制!
我们来分析一下上面这个例子:
闭包使得在上面这个例子bar是在自己定义的词法作用域以外的地方执行,但仍然持有对定义的词法作用域的引用。因此,在几位秒后变量baz被调用(调用的就是bar),不出意料它可以访问定义时的词法作用域,因此它也可以如预期般访问foo中的变量a。
上面这个例子是我为了体现闭包的特性而特意书写的,它看起来有的点刻意,但实际上我们身边处处存在闭包,比如咱们经常写的setTimeout,请看下面这个例子
function wait(message){
setTimeout( function timer(){
console.log(message);
},1000)
}
wait("Hello,closure");
这里将一个内部函数(名为timer)传递给setTimeout(..), timer具有涵盖wait(..)作用域的闭包,因此还保有对变量message的引用
wait(..)执行1000毫秒后,它的内部作用域并不会消失,timer函数依然保有wait(..)作用域的闭包.
实际上,在定时器,时间监听器.Ajax请求,跨窗口通信,Web Works或者其它的异步或同步任务中,只要使用了回调函数,你就能发现闭包的使用!!!
✨闭包的神奇之处--阻止垃圾回收
好了,通过以上的介绍,我相信你已经清楚地知道了闭包是什么了,能够知道闭包无处不在,下来我们将要探讨一下闭包的的神奇之处--阻止函数的垃圾回收。
首先,在函数执行之后,通常将函数的整个内部作用域销毁,为了不浪费空间,会有引擎的垃圾回收器用来释放不再使用的内存空间。
function foo(){
var a = 2;
function bar(){
console.log(a); // bar 捕获了 foo 的作用域(形成闭包)
}
return bar;
}
var a = 3;
var baz = foo() //执行foo,理论上foo的作用域应该被回收
baz(); //输出2 (闭包阻止了GC回收foo的作用域)
还是这个例子,按理来说,咱们在var baz = foo()
执行完后,foo()的整个内部作用域就会被销毁,可是闭包的神奇之处正是可以阻止这件事的发生,事实上foo()的作用域仍然存在,未被回收,这是为什么呢?原来是有bar()在使用.
拜bar()所声明的位置所赐,它拥有涵盖foo()内部作用域的闭包,使得该作用域能够不被回收,一直存活.所以当掉调用baz,baz()
时,它当然可以访问该作用域内的变量a!
下面来看一个没有闭包的函数案例
function noClosureExample() {
var temp = "I should be garbage collected";
console.log(temp); // 无内部函数引用 temp
}
noClosureExample(); // 执行后,temp 会被 GC 回收(无闭包引用)
这个普通函数没有闭包的存在,所以它在执行后,自然而然的就会被回收啦!
🚀闭包的应用:模块化
有许许多多的应用都会利用闭包的强大威力,下面我们一起来研究其中最强大的一个:模块
📦模块是什么
在JavaScript中, 模块是一个独立的代码单元,它将变量、函数、类等封装起来,通过导出和导入 机制与其它模块交互,模块的核心目的是实现代码复用、作用域隔离和依赖管理。
下面的代码是ES的模块实现的案例。
// math.js(模块定义)
var mathModule = (function() {
// 私有变量(外部无法访问)
var PI = 3.14;
// 公共方法
function sum(a, b) {
return a + b;
}
// 暴露公共接口
return {
sum: sum,
PI: PI
};
})();
闭包的体现:在mathModule中,IIFE 执行后,其内部作用域(包含 PI
和 sum
)本应被销毁,但由于返回的对象中引用了 sum
和 PI
,这些变量被闭包保留,供外部通过 mathModule
访问。
模块模式必须具备两个必要条件:
1.必须有外部的封闭函数,该函数必须至少被调用一次(每次调用都会创建一个新的模块实例) 2.封闭函数必须返回至少一个内部函数,这样内部函数才能在私有作用域中形成闭包,并且可以访问或者修改私有的状态
🔒模块的私有性:
模块的私有性是指:未被返回的变量(如 IIFE 内其他未暴露的变量)会被真正销毁,无法从外部访问,这就是模块的私有性。
var counterModule = (function() {
var count = 0; // 私有变量
function increment() {
count++;
}
function getCount() {
return count;
}
return { increment, getCount }; // 只暴露部分接口
})();
counterModule.increment();
console.log(counterModule.getCount()); // 1
console.log(counterModule.count); // undefined(无法直接访问私有变量)
increment
和 getCount
函数内部引用了 count
,因此 JavaScript 会保留 count
的引用。但是count
被定义在 IIFE 内部,外部无法直接修改或读取它,只有通过 increment()
和 getCount()
才能操作 count
,这就是闭包实现的数据封装,从而模块可以利用这个特性来形成其私有性。
🆕ES6的模块机制
在ES5中,JS没有原生的模块API,开发者只能通过IIFE(立即执行函数)、闭包、命名空间等方式模拟模块化,太过复杂的同时还会存在手动管理依赖和全局污染风险等问题,于是ES6,引入了原生模块语法,为模块增加了一级语法支持。
在ES6 通过模块系统进行加载时,ES6会将文件当作独立的模块来处理。每个模块都可以导入其他模块或特定的API成员,同样也可以导出自己的API成员。
具体语法如下:
1. 导出模块(export
)
// math.js
export const PI = 3.14; // 命名导出
export function sum(a, b) { return a + b; } // 命名导出
export default class Calculator { // 默认导出(每个模块仅限一个)
add(a, b) { return a + b; }
}
2. 导入模块(import
)
// app.js
import Calculator, { PI, sum } from './math.js'; // 混合导入(默认 + 命名)
console.log(sum(PI, 2)); // 5.14
const calc = new Calculator();
console.log(calc.add(1, 2)); // 3
3. 动态导入(按需加载)
// 动态加载模块(返回 Promise)
import('./math.js').then(module => {
console.log(module.sum(1, 2)); // 3
});
以下是两者的对比表格:
方案 | ES5(模拟) | ES6(原生模块) |
---|---|---|
实现方式 | IIFE + 闭包 | export / import 语法 |
作用域 | 手动管理(闭包) | 文件即模块(自动隔离) |
依赖管理 | 手动(<script> 标签顺序) | 静态分析(编译时确定依赖) |
私有性 | 通过闭包隐藏变量 | 默认隔离,支持显式导出 |
优化 | 无 Tree Shaking | 支持 Tree Shaking |
🎯总结
本文主要介绍了闭包的概念与闭包的重要应用:模块,其中具体介绍了闭包到底是什么,闭包能够在底层阻止函数作用域被回收,模块是什么,模块的私有性,以及在ES之后模块新的原生支持的使用方式,这些概念不论是在深入理解JS还是应对大厂的面试,都是非常之重要的,希望大家能够理清这些概念,读者也强烈建议各位可以取阅读《你不知道的JavaScript》这本经典著作,感谢你看到这里,祝福你能够在JavaScript的学习上收获满满!!!
🌇结尾
本文部分内容参考KYLE SIMPSON的《你不知道的JavaScript(上卷) 》
感谢你看到最后,最后再说两点~
①如果你持有不同的看法,欢迎你在文章下方进行留言、评论。
②如果对你有帮助,或者你认可的话,欢迎给个小点赞,支持一下~
我是3Katrina,一个热爱编程的大三学生
(文章内容仅供学习参考,如有侵权,非常抱歉,请立即联系作者删除。)
作者:3Katrina
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。