封闭了内心却包容了天下,闭包你并不孤独

2023-10-21 08:40

本文主要是介绍封闭了内心却包容了天下,闭包你并不孤独,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

原文:https://juejin.im/post/5acfeb7ff265da239867a97f

起点

本文之所以会写这种老生常谈的文章,是为了接下来的设计模式做铺垫。既然已经提笔了,就打算不改了,继续写下去,相信也一定有很多人对闭包这样的概念有些模糊,那就瞧一瞧、看一看

毕竟闭包和高阶函数这两种概念,在开发中是非常有分量的。好处多多,妙处多多,那么我们就不再兜圈子了,直接开始今天的主题,闭包&高阶函数

闭包

闭包是前端er离不开的一个话题,而且也是一个难懂又必须明白的概念。说起闭包,它与变量的作用域和变量的生命周期密切相关。这两个知识点我们也无法绕开,那么就一起了解下吧

变量作用域

首先变量作用域分为两类:全局作用域和局部作用域,这个没话说大家都懂。我们常说的变量作用域其实也主要是在函数中声明的作用域

  • 在函数中声明变量时没有var关键字,就代表是全局变量

  • 在函数中声明变量带有var关键字的即是局部变量,局部变量只能在函数内才能访问到

function fn() {var a = 110;     // a为局部变量console.log(a);  // 110}fn();console.log(a);     // a is not defined  外部访问不到内部的变量

上面代码展示了在函数中声明的局部变量a在函数外部确实无法拿到。小样儿的还挺嚣张,对于迎难而上的coder来说,还不信拿不下a了

客官,莫急,且听风吟。大家是否还记得在js中,函数可是“一等公民”啊,大大滴厉害

函数可以创造函数作用域,在函数作用域中如果要查找一个变量的时候,如果在该函数内没有声明这个变量,就会向该函数的外层继续查找,一直查到全局变量为止

所以变量的查找是由内而外的,这也形成了所谓的作用域链

var a = 7;function outer() {var b = 9;function inner() {var c = 8;        alert(b);        alert(a);    }    inner();    alert(c);   // c is not defined}outer();    // 调用函数

利用作用域链,我们试着去拿到a,改造一下fn函数

function fn() {var a = 110;     // a为局部变量return function() {console.log(a);    }console.log(a);  // 110}var fn2 = fn();fn2();      // 110

如此这般,这般如此,轻而易举,小case的事,就可以从外面访问到局部变量a了

那么到此为止,我们已经发现了闭包的其中一个意义:闭包就是能够读取其他函数内部变量的函数,嗯,没毛病,继续往下看

变量生命周期

在解决了上面如何拿到小样儿a的问题,我们不妨再把变量生命周期这个概念先简单地过一遍。

  • 对于全局变量来说,它的生命周期自然是永久的(forever),除非我们不高兴,主动干掉它,报销它。

  • 而对于在函数中通过var声明的局部变量来说,就没那么幸运了,当函数执行完毕,局部变量们就失去了价值,就被垃圾回收机制给当成垃圾处理掉了

  • 比如像下面这样的代码就很可怜

function fn() {var a = 123;    // fn执行完毕后,变量a就将被销毁了console.log(a);}fn();

虽然以上垃圾回收的过程我们无法亲眼看见,但是听者伤心闻者流泪啊。可不可以不要如此残忍,我愿倾其所有,换你三生三世。

悲伤的到来,我们无法拒绝,那就让我们想办法去改变这一切。现在再让我们来看下这段代码:

function add() {var a = 1;return function() {        a++;console.log(a);    }}var fn = add();fn();   // 2fn();   // 3fn();   // 4

这段代码最神奇的地方就是,当add函数执行完后,局部变量a并没有被销毁,而是依然存在,这其中到底发生了什么?让我们慢慢分析一下:

  • 当fn = add()时,fn返回了一个函数的引用,这个函数里有局部变量a

  • 既然这个局部变量还能被外部访问fn(),就没有必要把它给销毁了,于是就保留了下来

闭包是个好东西,可以完成很多工作,其中就包括一道网上常考的经典题目

<ul><li>1</li><li>2</li><li>3</li><li>4</li></ul><script>var aLi = document.getElementsByTagName('li');for (var i = 0; i < aLi.length; i++) {            aLi[i].onclick = function() {console.log(i);     // ?            };        }</script>

见过这道题的观众请举手,确实这道题的目的就是为了考对闭包的理解。上面的答案无论怎么点结果都是4。

这是因为li节点的onclick事件属于异步的,在click被触发的时候,for循环以迅雷不及掩耳盗铃的速度就执行完毕了,此时变量i的值已经是4了。

因此在li的click事件函数顺着作用域链从内向外开始找i的时候,发现i的值已经全是4了。

解决方法就需要通过闭包,把每次循环的i值都存下来。然后当click事件继续顺着作用域链查找的时候,会先找到被存下来的i,这样每一个li点击都可以找到对应的i值了。

   
<script>var aLi = document.getElementsByTagName('li');for (var i = 0; i < aLi.length; i++) {            (function(n) {    // n为对应的索引值                aLi[i].onclick = function() {console.log(n);     // 0, 1, 2, 3                };            })(i);  // 这里i每循环一次都存一下,然后把0,1,2,3传给上面的形参n        }</script>

其他作用

闭包应用非常广泛,我们这里就说一下大家熟知的,比如可以封装私有变量,可以把一些不需要暴露在全局的变量封装成私有变量,这样可以防止造成变量的全局污染。

var sum = (function() {var cache = {};     // 将cache放入函数内部,避免被其他地方修改return function() {var args = Array.prototype.join.call(arguments, ',');if (args in cache) {return cache[args];        }var a = 0;for (var i = 0; i < arguments.length; i++) {            a += arguments[i];        }return cache[args] = a;    }})();

除此之外相信很多人都见过一些库如jQuery,underscore他们的最外层都是类似如下样子的代码。

(function(win, undefined) {var a = 1;var obj = {};    obj.fn = function() {};
// 最后把想要暴露出去的内容可以挂载到window上    win.obj = obj;})(window);

是的,没错,利用闭包也可以做到模块化。另外还可以将变量的使用延长,再来看一个例子

var monitor = (function() {var imgs = [];return function(src){var img = new Image();        imgs.push(img);        img.src = src;    }})();
monitor('http://dd.com/srp.gif');

上面的代码是用于打点进行统计数据的情形,在之前的一些浏览器中,会出现打点丢失的情况,因为img是函数内的局部变量,当函数执行完后img就被销毁了,而此时可能http请求还没有发出。

所以遇到这种情况的时候,把img变量用闭包封装起来,就可以解决了。

内存管理

很多人都听过一个版本,就是闭包会造成内存泄漏,所以要尽量减少闭包的使用。

Just now就来为闭包来正名,不是你想象那样的:

  • 局部变量本来应该随着函数的执行完毕被销毁,但如果局部变量被封装在闭包形成的环境中,那这个局部变量就一直能存在。从我们上面实践得出的结果来看,这话说的没毛病

  • But之所以使用闭包是因为我们想要把一些变量存起来方便以后使用,这和放到全局下,对内存的影响是一致的,并不算是内存泄漏。如果在将来想回收这些变量,直接把变量设为null即可了

  • 还有就是在使用闭包的同时比较容易形成循环引用,如果闭包的作用域链中保存着一些DOM节点,此时就有可能造成内存泄漏。但这本身并非闭包的问题,也并非js的问题

  • 要怪就怪老版本的IE同志吧,它内部实现的垃圾回收机制采用的是引用计数策略。在老同志IE中,如果两个对象之间形成了循环引用,那么这两个对象都不能被回收,但循环引用造成的内存泄漏其本质也不是闭包的错

  • 同样要解决循环引用代理的内存泄漏问题,只需把循环引用中的变量设为null就好

上面就是我们替闭包的正名,闭包也不容易,被人用还不讨好。它明白,不是它的锅,它是不需要背的!

这不是终点。

虽然不是终点,但还是要搞个总结性发炎的,不然怎么对得起扁桃体兄

闭包

  • 是一个能够读取其他函数内部变量的函数,实质上是变量的解析过程(由内而外)

  • 可以用来封装私有变量,实现模块化

  • 可以保存变量到内存中

说完了闭包,我们先休息个一分钟,稍微理理思路。

然而真正的原因是由于文章内容过长,拆分成了姊妹篇。敬请关注后面的内容。

扫码关注,不错过任何一个干货

-- 前端技术江湖 -- 

这篇关于封闭了内心却包容了天下,闭包你并不孤独的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/253252

相关文章

JavaScript深入理解闭包

闭包(closure)是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现。 一、变量的作用域 要理解闭包,首先必须理解Javascript特殊的变量作用域。 变量的作用域无非就是两种:全局变量和局部变量。 Javascript语言的特殊之处,就在于函数内部可以直接读取全局变量。 Js代码   var n=999;   function f1

【JavaScript】在循环内使用闭包

================== 基本循环语句 ==================for (var i = 0; i < 5; i++) {console.log(i);}console.log(i);//这个大家应该很快就知道了,012345================== setTimeout与var语句的for循环 ==================for (var i

python闭包的作用

python闭包的作用 Python中的闭包是一种强大的编程概念,它在处理函数和作用域时提供了灵活性和便利性。下面是闭包的一些主要作用和应用: 1. 封装数据和函数 闭包可以将数据和操作这些数据的函数封装在一起,使得外部无法直接访问这些数据。这有助于创建数据的私有性。 def make_counter():count = 0def counter():<

【HDU】1317 XYZZY spfa判负环+floyd求传递闭包

传送门:【HDU】1317 XYZZY 题目分析:首先我们可以用spfa判最长路上是否有正权环,但是有正权环却不等价于能到达终点。这是我们还需要判断是否能从正权环中走到终点,这个可以用传递闭包搞定。如果没有正权环就看是否能从起点到终点就好了。 代码如下: #include <cstdio>#include <cstring>#include <algorithm>

前端组件化(一) 函数闭包 简单版

实现功能: 输入几个字, 就实时显示几个字 截图: 作用域隔离 为了使整个代码的作用域清晰明了, 封装成textCont 函数 <!DOCTYPE HTML><html><head><title>单通道数据流</title></head><body><input type="text" id="J_inp"><span id="J_text"></span></body><script sr

[Python]闭包与装饰器

闭包与装饰器 闭包: ​ python的一种独有写法,可以实现:对外部函数的局部变量进行’临时’存储 作用: ​ 可以"延长"函数内 局部变量的生命周期. 构成条件: 1. 有嵌套. 外部函数内要嵌套 内部函数.2. 有引用. 在内部函数中使用 外部函数的变量.3. 有返回. 在外部函数中, 返回 内部函数名, 即: 内部函数对象 格式def 外部函数

初探swift语言的学习笔记三(闭包-匿名函数)

作者:fengsh998 原文地址:http://blog.csdn.net/fengsh998/article/details/29353019 转载请注明出处 如果觉得文章对你有所帮助,请通过留言或关注微信公众帐号fengsh998来支持我,谢谢! 很多高级语言都支持匿名函数操作,在OC中的block也为大家所熟悉,然面在swift里好像是被

弦歌创投基金:与有识之士道济天下,实现价值

20世纪90年代,互联网的浪潮在中国兴起,时代精英们审时度势,精准抓住了此次时代赋予的红利,及时入场,一众大名鼎鼎的领军人物们由此诞生。然而,随着互联网+的不断普及和发展,这个领域的红利也日渐殆尽。若想成功突围,就必须找到新的方向和契机。   进入21世纪后,区块链这一新兴行业就开始慢慢的走到舞台中央,以其迅猛的发展趋势不断吸引着众多投资者的商业眼光。近年,“区块链+”一词逐渐出现在人们眼前。

深入理解JavaScript系列(16):闭包(Closures)

介绍 本章我们将介绍在JavaScript里大家经常来讨论的话题 —— 闭包(closure)。闭包其实大家都已经谈烂了。尽管如此,这里还是要试着从理论角度来讨论下闭包,看看ECMAScript中的闭包内部究竟是如何工作的。 正如在前面的文章中提到的,这些文章都是系列文章,相互之间都是有关联的。因此,为了更好的理解本文要介绍的内容,建议先去阅读第14章作用域链和第12章变量对象。 英

Javascript归纳与总结——this指向及其改变、new关键字与原型链、异步、闭包和函数防抖与节流

this指向及其改变 普通函数在调用时,this为obj.obj1.fun(),this->obj1,箭头函数在声明定义时this->obj。 Javascript中bind、call、apply區別-CSDN博客 new关键字与原型链  从原型链视角解读VueComponent与Vue关系_vue中重要的原型链关系-CSDN博客 prototype这个属性只有函数对象才有!(构造)