当前位置:网站首页>闭包(你不知道的JS)
闭包(你不知道的JS)
2022-07-30 05:45:00 【没事下辈子小心点】
到底什么是闭包
弄清楚词法作用域和闭包查找两种查找方式的不同
function foo() {
var a = 2;
function bar() {
console.log(a);
}
return bar;
}
var baz = foo();
baz(); // 2 —— 朋友,这就是闭包的效果。
bar() 显然可以被正常执行。但是在这个例子中,它在自己定义的词法作用域以外的地方执行。
这个函数在定义时的词法作用域以外的地方被调用。
闭包使得函数可以继续访问定义时的词法作用域。
说多一点:本来要被当垃圾回收的bar函数,因为外面调用内部函数,使得闭包作用,最后垃圾回收不了,可以继续使用而已。
无论通过何种手段将内部函数传递到所在的词法作用域以外,它都会持有对原始定义作用域的引用,无论在何处执行这个函数都会使用闭包。词法作用域保持完整的过程就是闭包。
上面的return是一种方式,这里也可以再函数运行中途用外面的函数和内置函数反应产生效果。
function foo() {
var a = 2;
function baz() {
console.log(a); // 2
}
bar(baz);
}
function bar(fn) {
fn(); // 妈妈快看呀,这就是闭包!
}
或者采取间接的方法:
var fn;
function foo() {
var a = 2;
function baz() {
console.log(a);
}
fn = baz; // 将 baz 分配给全局变量
}
function bar() {
fn(); // 妈妈快看呀,这就是闭包!
}
foo();
bar(); // 2
无论何时何地,如果将函数(访问它们各自的词法作用域)当作第一
级的值类型并到处传递,你就会看到闭包在这些函数中的应用。在定时器、事件监听器、
Ajax 请求、跨窗口通信、Web Workers 或者任何其他的异步(或者同步)任务中,只要使
用了回调函数,实际上就是在使用闭包!
循环和闭包
延迟函数的回调会在循环结束时才执行。
for (var i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i);
}, i * 1000);
}
代码中到底有什么缺陷导致它的行为同语义所暗示的不一致呢?
缺陷是我们试图假设循环中的每个迭代在运行时都会给自己“捕获”一个 i 的副本。但是根据作用域的工作原理,实际情况是尽管循环中的五个函数是在各个迭代中分别定义的,但是它们都被封闭在一个共享的全局作用域中,因此实际上只有一个 i。
我们需要更多的闭包作用域,特别是在循环的过程中每个迭代都需要一个闭包作用域。
第 3 章介绍过,IIFE 会通过声明并立即执行一个函数来创建作用域。
那么为了达到效果:
这样也不行:
for (var i = 1; i <= 5; i++) {
(function () {
setTimeout(function timer() {
console.log(i);
}, i * 1000);
})();
}
我们现在显然拥有更多的词法作用域了。的确每个延迟函数都会将 IIFE 在每次迭代中创建的作用域封闭起来。
如果作用域是空的,那么仅仅将它们进行封闭是不够的。仔细看一下,我们的 IIFE 只是一个什么都没有的空作用域。它需要包含一点实质内容才能为我们所用。
for (var i = 1; i <= 5; i++) {
(function () {
var j = i;
setTimeout(function timer() {
console.log(j);
}, j * 1000);
})();
}
// 行了!它能正常工作了!。
// 可以对这段代码进行一些改进:
for (var i = 1; i <= 5; i++) {
(function (j) {
setTimeout(function timer() {
console.log(j);
}, j * 1000);
})(i);
}
当然,这些 IIFE 也不过就是函数,因此我们可以将 i 传递进去,如果愿意的话可以将变量名定为 j,当然也可以还叫作 i。
无论如何这段代码现在可以工作了。
在迭代内使用 IIFE 会为每个迭代都生成一个新的作用域,使得延迟函数的回调可以将新的作用域封闭在每个迭代内部,
每个迭代中都会含有一个具有正确值的变量供我们访问。
闭包+块作用域“
进行了五次循环,有五个块作用域,但是因为循环后之前用的是同一个作用域,所以都输出6,现在是5个,每次挂载回调函数的时候,都对J进行赋值,所以最后打印(回调函数)的是这5个作用域里面分别的12345
for (var i = 1; i <= 5; i++) {
let j = i; // 是的,闭包的块作用域!
setTimeout(function timer() {
console.log(j);
}, j * 1000);
}
直接进行每个作用域的声明,而且分别用上一个作用域的i对下一个作用域进行赋值
for (let i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i);
}, i * 1000);
}
模块
闭包和模块暴露,顾名思义就可以了,如果你了解了上面写的东西的话。
暴露的是函数(模块)内部的函数,或者是属性,必须执行一次函数以后,实例创建完才有内部作用域和闭包。
类似于公共API
import 可以将一个模块中的一个或多个 API 导入到当前作用域中,并分别绑定在一个变量上(在我们的例子里是 hello)。module 会将整个模块的 API 导入并绑定到一个变量上(在我们的例子里是 foo 和 bar)。
export 会将当前模块的一个标识符(变量、函数)导出为公共 API。
这些操作可以在模块定义中根据需要使用任意多次。
模块文件中的内容会被当作好像包含在作用域闭包中一样来处理,就和前面介绍的函数闭包模块一样。
边栏推荐
- QT串口动态实时显示大量数据波形曲线(五)========“最终完美解决版”
- 思谋面试准备
- 【总结】工业检测项目中如何选择合适的损失函数
- QT连载3:基于QT和STM32H750的LORA试验平台(2)
- openssl 1.1.1编译语句
- 迪文串口屏幕制作(连载一)=====准备工作
- R - GIS: how to use R language implementation of GIS geospatial analysis and model prediction
- The application of Meta analysis in the field of ecological environment
- 为什么会出现梯度爆炸和梯度消失现象?怎么缓解这种现象的发生?
- HSPF model application
猜你喜欢

华秋电子成为开放原子开源基金会openDACS捐赠人,共建 openDACS开源生态

昆仑通态屏幕制作(连载5)---基础篇(串口接收,文本与灯显示)

边境的悍匪—机器学习实战:第二章 端到端的机器学习项目

How does MATLAB display nii file slice information in the image?

R-GIS: 如何用R语言实现GIS地理空间分析及模型预测

边境的悍匪—机器学习实战:第十五章 使用CNN和RNN处理序列

Configure MMdetection environment and train

Pytorch(一):动态图机制以及框架结构

【江科大自化协stm32F103c8t6】笔记之【入门32单片机及GPIO初始化参数配置】

jvm之逃逸分析
随机推荐
边境的悍匪—机器学习实战:第八章 降维
基于PyTorch深度学习无人机遥感影像目标检测、地物分类及语义分割
昆仑通态屏幕制作(连载3)---基础篇(按钮串口发送)
1.03 original Acegi security mechanism
求职准备知识点
Insertion Sort in Classic Sort
ssh 脚本 空格字符转换
华秋第八届硬创赛与安创加速器达成战略合作,助力硬科技项目成长
基于遥感解译与GIS技术环境影响评价图件制作(最新导则)
DeepLearing4j's deep learning Yolo Tiny realizes target detection
CNN经典模型发展进程
【Qingdao Station】High-level application of SWAT model and modeling of areas without data, uncertainty analysis and climate change, improvement of land use surface pollution impact model and case analy
vs编译boost库脚本
openssl 1.1.1编译语句
通过位运算进行字符大小写转换
OpenLayers 初学者指南,源码测试可用
openssl1.1.1ARM dual compilation
逻辑右移和算术右移区别
influxDB运维记录
QT连载3:基于QT和STM32H750的LORA试验平台(2)