闭包是什么?五分钟带你了解闭包
闭包
前言
闭包对每个前端来说都是一个绕不开的话题。学习之初也因为搞清闭包的概念耗费了不少精力,今天写一篇博客来记录本人对闭包的理解,笔者水平有限,若有疏漏及错误,愿不吝赐教。
什么是闭包?
你可以在一个函数里面嵌套另外一个函数。嵌套(内部)函数对其容器(外部)函数是私有的。它自身也形成了一个闭包。一个闭包是一个可以自己拥有独立的环境与变量的的表达式(通常是函数,因为ES6有了块级作用域的概念)。
可以先点击上面链接查看MDN对闭包的讲述。
闭包在JavaScript高级程序设计(第3版)中是这样描述:闭包是指有权访问另一个函数作用域中的变量的函数。
这样闭包的作用就很明显了
- 可以在函数的外部访问到函数内部的局部变量。
- 让这些变量始终保存在内存中,不会随着函数的结束而自动销毁。
我们来写一个简单的例子:
console.log('closure');
function add() {var count = 0;return function() {count += 1;return count;};
}
var a = add();
function fun() {alert(a());
}
fun();
fun();
我们来解释一下这段代码:
定义了add() 函数,函数内定义了count,返回值也是一个函数。子匿名函数引用外部变量count,因此形成了闭包。
当下面我们定义并赋值a = add()
时,add()
函数执行且仅执行一次。执行后a被赋值(匿名函数)了
上面代码中function() { count += 1; return count; };
部分就形成了一个闭包。引用了函数外部变量count
。因此count在函数执行结束也不会销毁,一直存在于内存中。然后每次调用a()函数时,返回的都是内层匿名函数(闭包)的执行结果,因为内层匿名函数在外层函数的内部,所以即使外层函数执行完了,第二个函数依然能访问count(JS设计的作用域链,当前作用域能访问上级的作用域)。
闭包经典案例
该案例是牛客网题库的一道题:
<ul><li>i</li><li>i</li><li>i</li><li>i</li></ul>
js实现:要求点击每个li标签,弹出对应的索引。(0,1,2,3)
实现1:
var elements = document.getElementsByTagName('li');
for (var i = 0; i < elements.length; i++) {elements[i].onclick = function () {console.log(i);alert(i);};
}
获取dom元素,进行遍历不就可以了吗?但是当我们自信的点击标签时候发现弹出都是4……。整个人都觉得不好了!
ps:有人疑惑不应该都是3吗?为什么是4? 这是因为for循环的执行顺序都是以i++结束的,最后一次不进入循环。但是var i 是在全局中的,因此i会被赋值为4.具体for循环知识可以自行搜索这里不做赘述。
下面是这个题目的解答:
每个li标签的onclick事件执行时,本身onclick绑定的function的作用域中没有变量i,i为undefined,则解析引擎会寻找父级作用域,发现父级作用域中有i,且for循环绑定事件结束后,i已经赋值为4,所以每个li标签的onclick事件执行时,alert的都是父作用域中的i,也就是4。这是作用域的问题。这里onclick事件绑定的函数形成闭包,但引用的变量i是全局的。
闭包真正的含义是,如果一个函数访问了此函数的父级及父级以上的作用域变量,就可以称这个函数是一个闭包。
这里其实是一个作用域问题,闭包中引用的变量不会消失,所以我们把每次的i都保存到变量中,就可以实现想要的结果。
for (let i = 0; i < elements.length; i++) {elements[i].onclick = function () {return (function (n) {console.log(n);alert(n);})(i);};
}
这样我们就使用闭包将i作为参数传递到匿名函数(闭包)中,每个点击事件都有了一个局部的num变量,保存的即相应的i值。
拓展:
我们可以直接将for循环的var改为let就可以实现想要的效果。let的到来,让在循环体内创建闭包具有了更好的可读性。可搜索学习let和const的相关知识。或亲自动手,将let通过babel编译查看相关结果进行研读。
有关let的知识点会在以后的博文中进行整理。
闭包造成的内存泄漏问题
闭包怎么引起内存泄漏?谁会存在内存泄漏?
我们借助垃圾回收机制的标记清除法来看一下:
示例:
function fn () {let count = 1function fun () {count ++console.log(`函数内部调用${count}次`)}return fun
}
const result = fn();
result() //2
result() //3
我们来分析一下:
result
是一个全局变量,代码执行完毕不会立即销毁result
可以找到fn()
函数,fn()
函数return fun()
,因此 fun()也不会销毁。fun()
引用到了父级作用域的count
,因此,在引用的count
便不会被垃圾回收机制回收。
此时,闭包就引起了内存泄漏问题
正常情况下,一般的局部变量函数执行完就会销毁,但是我们这里的result
没有销毁,所以它引用到的count
也会留存在内存当中。
闭包的应用
闭包的实际应用主要是函数工厂,以及模拟私有方法。
闭包实现函数工厂
举例:
function makeAdder(x) {return function(y) {return x + y;};
}var add5 = makeAdder(5);
var add10 = makeAdder(10);console.log(add5(2)); // 7
console.log(add10(2)); // 12
这是MDN官网的一个makeAdder(x)
函数,接受一个参数x
,并返回新的函数,返回的函数接受一个参数y
,并返回x+y
的值。
从本质上讲,makeAdder
是一个函数工厂 — 他创建了将指定的值和它的参数相加求和的函数。在上面的示例中,我们使用函数工厂创建了两个新函数 — 一个将其参数和 5 求和,另一个和 10 求和。
add5 和 add10 都是闭包。它们共享相同的函数定义,但是保存了不同的词法环境。在 add5 的环境中,x 为 5。而在 add10 中,x 则为 10。
应用案例:假如我们想在页面添加几个按钮用来调整页面字号。我可以给body元素设置font-size,然后通过em单位设置页面中其他元素的字号:
body {font-family: Helvetica, Arial, sans-serif;font-size: 12px;
}
h1 {font-size: 1.5em;
}
h2 {font-size: 1.2em;
}
然后可以对body的font-size进行调整:
function makeSizer(size) {return function() {document.body.style.fontSize = size + 'px';};
}var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);
这样,size12,size14,size16就可以分别将body字体调整为12,14,16px,再将它们添加到点击事件上。
document.getElementById('size-12').onclick = size12;
document.getElementById('size-14').onclick = size14;
document.getElementById('size-16').onclick = size16;
这样就实现了想要的效果。这就是一个简单的闭包实现函数工厂的案例。
闭包模拟私有方法
var Counter = (function () {var privteCounter = 0;function changeBy (val) {privteCounter += val;}return {increment: function () {changeBy(1)},decrement: function () {changeBy(-1)},value: function () {return privteCounter}}
})()console.log(Counter.value()); /* logs 0 */
Counter.increment();
Counter.increment();
console.log(Counter.value()); /* logs 2 */
Counter.decrement();
console.log(Counter.value()); /* logs 1 */
在之前的示例中,每个闭包都有它自己的词法环境;而这次我们只创建了一个词法环境,为三个函数所共享:Counter.increment
,Counter.decrement
和 Counter.value
。
该共享环境创建于一个立即执行的匿名函数体内。这个环境中包含两个私有项:名为 privateCounter
的变量和名为 changeBy 的函数。这两项都无法在这个匿名函数外部直接访问。必须通过匿名函数返回的三个公共函数访问。
这三个公共函数是共享同一个环境的闭包。多亏 JavaScript 的词法作用域,它们都可以访问 privateCounter
变量和 changeBy
函数。
备注: 你应该注意到我们定义了一个匿名函数,用于创建一个计数器。我们立即执行了这个匿名函数,并将他的值赋给了变量Counter。我们可以把这个函数储存在另外一个变量makeCounter中,并用他来创建多个计数器。
var makeCounter = function () {var privteCounter = 0;function changeBy(val) {privteCounter += val;}return {increment: function () {changeBy(1);},decrement: function () {changeBy(-1);},value: function () {return privteCounter;},};
};var Counter1 = makeCounter();
var Counter2 = makeCounter();
console.log(Counter1.value()); /* logs 0 */
Counter1.increment();
Counter1.increment();
console.log(Counter1.value()); /* logs 2 */
Counter1.decrement();
console.log(Counter1.value()); /* logs 1 */
console.log(Counter2.value()); /* logs 0 */
请注意两个计数器 Counter1 和 Counter2 是如何维护它们各自的独立性的。每个闭包都是引用自己词法作用域内的变量 privateCounter 。
每次调用其中一个计数器时,通过改变这个变量的值,会改变这个闭包的词法环境。然而在一个闭包内对变量的修改,不会影响到另外一个闭包中的变量。
备注: 以这种方式使用闭包,提供了许多与面向对象编程相关的好处 —— 特别是数据隐藏和封装。
闭包应用部分是引用MDN的案例,大家可以移步阅读(一定要读
)。
今天的总结分享就到这里,继续努力搬砖!只作个人总结所用,有不足与疏漏请评论区补充。
闭包是什么?五分钟带你了解闭包相关推荐
- c++ map用法_Python专题——五分钟带你了解map、reduce和filter
点击上方蓝字,和我一起学技术.今天是Python专题第6篇文章,给大家介绍的是Python当中三个非常神奇的方法:map.reduce和filter.不知道大家看到map和reduce的时候有没有什么 ...
- 五分钟学会python函数_五分钟带你搞懂python 迭代器与生成器
前言 大家周末好,今天给大家带来的是Python当中生成器和迭代器的使用. 我当初第一次学到迭代器和生成器的时候,并没有太在意,只是觉得这是一种新的获取数据的方法.对于获取数据的方法而言,我们会一种就 ...
- 干货:五分钟带你看懂NestedScrolling嵌套滑动机制
Android NestedScrolling嵌套滑动机制 Android在发布5.0之后加入了嵌套滑动机制NestedScrolling,为嵌套滑动提供了更方便的处理方案.在此对嵌套滑动机制进行详细 ...
- python内置函数与闭包_Python进阶(五)----内置函数Ⅱ 和 闭包
Python进阶(五)----内置函数Ⅱ 和 闭包 一丶内置函数Ⅱ ####内置函数#### 特别重要,反复练习 ###print() 打印输入 #sep 设定分隔符 # end 默认是换行可以打印到 ...
- 五分钟带你理解python闭包(闭环)
闭环是一种现象 要满足闭环需要达到三个条件: 1,外函数中定义了一个内函数 2,内函数中运用了外函数的局部变量 3,外函数的返回值是内函数的引用 话不多说,我接下来用一段代码让大家明白 def tex ...
- 二道波哥-五分钟带你了解好省
什么是好省? 1.好省是一款导购APP,依托于淘宝天猫平台,为广大用户提供海量优惠券,致力于帮助用户买到最具性价比的网购商品.与此同时,好省还是一个创业平台,在这里,任何人都可以0门槛.0投资创业,只 ...
- 五分钟带你玩转Zabbix6.0容器化HA集群
图片 蔡斯 | Zabbix开源社区签约专家 Zabbix资深玩家,SRE高级运维,架构师.精通服务组件监控.模板制作及告警治理. 擅长领域:Zabbix API定制化开发,对接企业蓝鲸,JMS保垒机 ...
- 五分钟带你了解Django框架设计思想!
Python编程语言的持续火爆,在最新Tiobe编程语言排行榜中位列第五,热度持续增加,尤其是在 AI 和 大数据时代,Python是 AI 和大数据时代的第一开发语言.这已经是一个不争的事实了,唯一 ...
- 五分钟带你了解前端开发,以及前端开发的发展机会
前端开发 前端开发是创建Web页面或app等前端界面呈现给用户的过程,通过HTML,CSS及JavaScript以及衍生出来的各种技术.框架.解决方案,来实现互联网产品的用户界面交互 .它从网页制作 ...
最新文章
- Java 学习(20)--异常 / IO 流
- 多元二次方程 python_Python 二次方程
- asp批量生成html静态页面方法
- Angular应用i18n - internationalization翻译的实现单步调试
- SAP CRM pricing read的实现逻辑
- winform Outlookbar
- java vector list,Java基础之:List——ArrayList Vector
- python中如何调用类_python中如何调用类的方法
- java旅客接口_GitHub - s1mplecc/checkin-scheduler: 使用 Java 实现的旅客人数分布预估与值机排班调度算法...
- 【技术】Java打印菱形
- 浅谈软件开发的四大要素
- 华为OJ-奥运会排行榜C++
- 如何把Eclipse改成中文版
- 富士通Fujitsu DPK750 Pro 打印机驱动
- 论文-公式对齐和右侧自动编号
- 中国工程咨询行业十四五投资机会与运营风险评估报告2022-2028年
- 微信小程序商城项目实战(第四篇:商品详情页)
- 卷死我了,终于毕业了!!
- 2017 php 免费空间,免费空间免费php空间
- 子网划分及NAT技术总结
热门文章
- VS2008 使用小技巧-------快捷键
- WarShall算法求传递闭包(可达矩阵)
- 解决Vivado implementation拥塞的策略方法(一)
- ConnectionString
- 【Android】【自动填充】自定义AutofillService(二):编写AutofillService代码
- 经典PID控制算法原理以及优化思路
- mysqlbinlog如何恢复误删的数据
- AtCoder题解 —— AtCoder Beginner Contest 182 —— D - Wandering
- Hash(散列)冲突解决 线性探测再散列和二次探测再散列
- 可以安心过中秋了!黎曼猜想亡不了区块链!