JavaScript 来好好盘一盘闭包!

今天JavaScript栏目好好介绍闭包

JavaScript 来好好盘一盘闭包!

前言

想要深入学习JavaScript这门语言,闭包这个概念几乎是绕不开的关键,今天就让我们一起好好地盘一盘,闭包到底是什么东西。
如果是零基础的小伙伴,可以先看看前一篇文章,帮助你更好的理解本文的内容:

【JavaScript】有趣的作用域和提升

了解一下闭包

1. 什么是闭包

我们先来看看闭包在MDN中的定义:

一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)

闭包的定义非常晦涩难懂,如果通俗的解释一下的话,就是:

一个定义在函数内部的函数和所在作用域的组合称为闭包。  

这么讲可能还是非常抽象,不过我们先有个概念,下面会详细解释这个概念。对于闭包,其实还有另一种解释:

立即学习“Java免费学习笔记(深入)”;

一个函数中声明了一个内部函数并返回,外部可以通过这个内部函数访问到内部的值,这样的函数,我们称作闭包

区别于第一种解释,第二种解释把函数本身称作为闭包,这样的解释也合理,但更准确的话其实我们可以把这个函数称作闭包函数。这样的话,其实两种解释都没有问题,也可以同时存在了。

2. 感受一下闭包

看完了闭包的概念,我们通过实际的例子感受一下,闭包长什么样?闭包产生的条件?

闭包长什么样?

function outer(){    var a=0;    function inner(){        console.log(++a);    }    return inner;}var f=outer();f();  //1f();  //2复制代码

登录后复制

我们先来看下这个例子里,闭包是如何体现的。我们首先定义了outer函数,然后在outer函数的内部声明了一个inner函数并返回。

在外部,我们执行outer函数,并将返回的inner函数保存在了f中,现在的f就是我们说的闭包函数了。

在例子中,我们执行了两次f函数,分别输出了1和2,说明我们在执行f函数中的console.log(++a);语句时,根据我们之前对作用域的解释,会去函数所在的作用域链上寻找变量,最终在outer函数中找到了这个变量,并进行了增值操作。

同时需要注意的是,我们输出的结果并不是两次1,说明我们的作用域是共享的,可以看成outer的作用域延伸到了外部。

outer作用域本来应该在执行结束后释放,但根据GC机制(我们这里先不展开介绍)会发现:

我准备释放outer了,但a变量好像被引用了,而且外部还保存了起来不知道什么时候调用?好叭,那我就先不释放了。

闭包就这样诞生了,在作用域消失之后,外部依然可以访问作用域中的变量

闭包产生的条件?

我们根据刚刚的例子和解释总结一下,产生一个闭包需要哪些条件:

在外部函数中声明一个内部函数,并返回内部函数。
这里的outer就是外部函数,inner就是返回的内部函数。内部的函数引用了外部函数中的变量。
我们的例子中,inner函数就引用了变量a。外部函数需要被执行,创建一个闭包,同时返回的内部函数需要被保存起来。
外部函数outer需要被执行,同时需要把返回的内部函数给保存起来,也就是我们例子中的var f=outer()。

只有同时满足了这三个条件,才能产生闭包。看似很难达成,事实上,我们实际代码中经常不经意间就能产生闭包。

通过刚刚的介绍,相信大家应该对闭包有了一个概念,接下来就让我们出发,深入的了解一下闭包的方方面面。

深入体验一下闭包

1.创建多个闭包

在刚刚的例子中,我们创建了一个闭包,执行多次闭包函数后,增加的都是同一个作用域中的变量a,那么我们试试创建多个闭包会怎么样:

function outer() {  var a = 0;  function inner() {    console.log(++a);  }  return inner;}var f1 = outer();var f2 = outer();f1();  //1f2();  //1复制代码

登录后复制

这段代码在刚刚的例子上进行了改动,我们执行了两次外部函数outer,并分别用不同的变量f1和f2保存。当执行f1和f2时会发现,输出的结果都是1,说明f1和f2的作用域是独立的,f1和f2属于两个不同的闭包,我们用一张图来理解下:JavaScript 来好好盘一盘闭包!

当分别创建f1和f2时,调用了两次outer函数,创建了两个不同的上下文。而当f1和f2分别执行时,根据作用域的查找规则,会去对应的作用域中查找变量,并执行增值输出,所以最终两个值均为2;

2.使用闭包实现模块化

我们知道,作用域的外部无法拿到作用域内部的值,而通过闭包,我们可以把作用域我们需要的值或者方法暴露出去,比如:

function outer() {  var myNumber = 0;  function addNumber() {    myNumber++;  }  function getNumber() {    return myNumber;  }  return {    addNumber, getNumber  }}var module = outer();module.addNumber();module.addNumber();module.getNumber();复制代码

登录后复制

在这个例子中,我们同样定义了一个外部函数outer,另外还分别定义了两个函数addNumber和getNumber,用于增加和获取变量myNumber。

当我们执行outer()语句的时候,会创建一个上下文,同时把内部返回的对象保存在module变量上,此时我们就创建了一个闭包,以及一个包含方法addNumber和getNumber的对象。

由于外部是无法直接访问变量myNumber的,但由于闭包的原因,addNumber和getNumber是可以访问到这个变量的,因此我们成功的把变量myNumber隐藏了起来,并且对外只提供了增加和获取myNumber值的方法。

试着用闭包解决问题

通过刚刚的例子,相信大家应该对闭包有了一定了解,接下来我们试着运用闭包来解决实际问题,先看一下例子:

for (var i = 0; i  {        console.log(i);      }, 500);    }复制代码

登录后复制

这是一个十分容易令人误解的例子。接触过的小伙伴肯定都知道,最后会输出两次2而不是依次输出0和1,我们来看看为什么会这样。
首先,外部是一个for循环,执行了两次语句。

for (var i = 0; i < 2; i++) {    ... //  执行两次}复制代码

登录后复制

在函数的内部,我们调用了setTimeout函数,关键的地方来了,这个函数是一个异步函数,并不会马上执行,所以实际上等外部的for循环执行结束了,才会真的执行setTimeout中的函数。还有第二个关键点在于,在for循环中,var定义的变量相当于定义在全局,而不存在块级作用域。那么刚刚的代码就可以近似的看成这样了。

var i=0;i++;    //i=1i++;    //i=2console.log(i);console.log(i);复制代码

登录后复制

非常直接粗暴,但可以很清晰的看出输出结果为何是两次2了,因为大家共用了同一个作用域,i的值被覆盖了。那么知道了问题出在哪里,我们试着用上我们刚刚学习的闭包,来创建不同的作用域:

   for (var i = 0; i < 2; i++) {      function outer() {        var j = i;        setTimeout(function inner() {          console.log(j);        }, 500);      }      outer();    }复制代码

登录后复制

我们按照闭包的样式对刚刚的代码进行了改造,这里的setTimeout并不直接就是inner函数,这是因为它在这里起到了定义inner函数,并保存执行inner函数的功能。

我们可以看到,最终结果依次输出了0和1,说明我们的闭包是成功了的,但这样的闭包比较臃肿,我们试着提高一下,写的更加优雅一点:

   for (var i = 0; i < 2; i++) {        (function() {   //用自执行函数代替了`outer`函数的定义和执行两个步骤            var j = i;            setTimeout(function inner() {                console.log(j);            }, 500);        })();    }复制代码

登录后复制

还可以再简化一下:

for (var i = 0; i < 5; i++) {        for (var i = 0; i < 2; i++) {            (function(j) {//用自执行函数代替了`outer`函数的定义和执行两个步骤                setTimeout(function inner() {                    console.log(j);                }, 500);            })(i);//var j=i的步骤,通过传入i值替换        }    }复制代码

登录后复制

这样就大功告成了!

总结

本篇首先介绍了闭包的定义以及不同人对闭包的理解,之后介绍了闭包产生的原因并总结了三个条件,之后举例说明了如何创建多个闭包和通过闭包实现模块,最后讲述了如何通过闭包解决for循环中使用异步函数依次输出值的问题。其实闭包没有想象的那么可怕,只要你愿意静下心去探索去了解,闭包也会对你敞开大门~

写在最后

都看到这里了,如果觉得对你有帮助的话不妨点个赞支持一下呗~

以后会陆续更新更多文章和知识点,感兴趣的话可以关注一波~

如果哪里有错误的地方或者描述不准确的地方,也欢迎大家指出交流~

相关免费学习推荐:javascript(视频)

以上就是JavaScript 来好好盘一盘闭包!的详细内容,更多请关注【创想鸟】其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至253000106@qq.com举报,一经查实,本站将立刻删除。

发布者:PHP中文网,转转请注明出处:https://www.chuangxiangniao.com/p/2725167.html

(0)
上一篇 2025年3月7日 23:20:48
下一篇 2025年2月27日 18:28:29

AD推荐 黄金广告位招租... 更多推荐

相关推荐

  • 10个非常实用的javascript特性

    你可能刚上手 JavaScript,或者只是曾经偶尔用过。不管怎样,JavaScript 改变了很多,有些特性非常值得一用。 这篇文章介绍了一些特性,在我看来,一个严肃的 JavaScript 开发者每天都多多少少会用到这些特性。 参考资料…

    2025年3月7日
    200
  • javaScript面向对象的三个基本特征介绍

    了解过面向对象的同学应该都知道,面向对象三个基本特征是:封装、继承、多态,但是对于这三个词具体可能不太了解。对于前端来讲接触最多的可能就是封装与继承,对于多态来说可能就不是那么了解了。 封装 在说封装之先了解一下封装到底是什么? 什么是封装…

    2025年3月7日
    200
  • 如何设计算法?常见的算法范式介绍

    如何设计算法?下面本篇文章给大家分析一下常见的算法范式。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。 首先明确三个概念: 算法: 按步骤解决问题的过程。 范式: 思考问题的模式。 算法范式: 为问题构建高效解决方案的常规…

    2025年3月7日
    200
  • 你需要知道的关于javascript计时器的所有内容

    先从一个面试题开始: JavaScript 面试题:setTimeout 和 setInterval 的源代码是在哪里实现的? (不能百度和谷歌 ) 在继续往下看之前,请先在你的头脑中回答问题 你的答案可能会是 V8(或其他VM),但很遗憾…

    2025年3月7日
    200
  • Node.js 15正式版发布,将替代Node.js 14成为当前的的稳定发行版

    前两天,Node.js官方发布了Node.js 15的正式版本,Node.js 15 将替代 Node.js 14 成为当前的的稳定发行版,后者将在本月晚些时候升级为 LTS(长期支持)版本。如果大家想体验下Node.js 15 的最新功能…

    2025年3月7日
    200
  • js数组怎么去重?

    js数组去重方法:1、使用ES6的Set对象去重;2、利用for嵌套for,然后splice去重;3、利用indexOf去重;4、利用对象的属性不能相同的特点进行去重;5、利用Map数据结构去重;6、利用reduce+includes去重。…

    2025年3月7日
    200
  • js数组去重方法的总结

    js数组去重的方法:1、利用ES6 Set去重;2、利用for嵌套for,然后splice去重;3、利用indexOf去重;4、利用sort()去重;5、利用对象的属性不能相同的特点进行去重等等。 推荐:《js视频教程》 数组去重,一般都是…

    2025年3月7日
    200
  • JavaScript 中判断变量是否为数字

    今天javascript栏目如何判断变量是否为数字。 大家都说简历没项目写,我就帮大家找了一个项目。 简介 JavaScript 是一种动态类型语言,这意味着解释器在运行时确定变量的类型。实际上,这也允许我们在相同的代码中使用相同的变量来存…

    2025年3月7日
    200
  • 详解JS中的JSON和JSONP

    简单地使用json并不能支持跨域资源请求,为了解决这个问题,需要采用jsonp数据交互协议。众所周知,js文件的调用不受跨域与否的限制,因此如果想通过纯web端跨域访问数据,只能在远程服务器上设法将json数据封装进js格式的文件中,供客户…

    2025年3月7日
    200
  • 详解构建可运行的JavaScript规范的方法

    编程不仅仅是给计算机下达如何完成一项任务的指令,它还包括以一种精确的方式与他人交流思想,甚至是与未来的自己。这样的交流可以有多个目标,也许是为了共享信息,或者只是为了更容易地修改—如果你不理解或不记得很久以前做过什么,那么就很难修改。 当我…

    2025年3月7日
    200

发表回复

登录后才能评论