# 闭包不是魔法
这篇文章使用一些简单的代码例子来解释JavaScript闭包的概念,即使新手也可以轻松参透闭包的含义。

其实只要理解了核心概念,闭包并不是那么的难于理解。但是,网上充斥了太多学术性的文章,对于新手来说,看完这些文章可能会更加一头雾水。

这篇文章面向的是使用主流开发语言的程序员,如果你能读懂下面这段代码,恭喜你,你可以开始JavaScript闭包的学习之旅了。

```javascript
function sayHello(name) {
var text = 'Hello' + name;
var say = function() {
console.log(text);
}
say();
}
sayHello('Joe');
```
我相信你一定看懂了,那我们就开始吧!

# 闭包的一个例子
举例之前,我们先用两句话概括一下:

- 闭包是支持`一类函数`特性的一种方式(如果你还不知道什么是一类函数,请自行百度);它是一个表达式,这个表达式可以在其作用域(当它被初次定义时)内引用变量,或者被赋值给一个变量,或者被当做一个变量传递给某个函数,甚至被当作一个函数的执行结果被返回出去。
- 闭包也可以看作是某个函数被调用时分配的栈帧,而且当这个函数返回结果之后它也不会被回收(就好像它被分配给了堆,而不是栈)

下面的例子返回了对一个方法的引用:

```javascript
function sayHello2(name){
var text= 'Hello' + name; //局部变量
var say=function(){
console.log(text);
}
return say;
}
var say2=sayHello2('Bob');
say2();//logs='Hello Bob'
```
我想大多数JavaScript程序员都能理解上面代码中一个函数的引用是如何被赋值给一个变量(`say2`)的。如果你不清楚的话,最好在继续了解闭包之前弄清楚。使用C语言的程序员或许会认为这个函数是指向另一个函数的指针,并且变量`say`和`say2`也同样是指向函数的指针。

然而C语言中指向函数的指针和JavaScript中对一个函数的引用有很大的不同。在JavaScript中,你可以把引用函数的变量当作同时拥有两个指针:一个指向函数,另一个隐形地指向闭包。

上面的代码中生成了一个闭包是因为匿名函数`function(){console.log(text);}`被定义在了另外一个函数`sayHello2()`中。在JavaScript中,如果你在一个函数中定义了另外一个函数,那么你就创建了一个闭包。

在C语言或者其他流行的开发语言当中,函数返回之后,所有局部变量都不能再被访问,因为栈帧已经被销毁了。

在JavaScript中,如果在一个函数中定义了另外一个函数,即使从被调用的函数中返回,局部变量依然能够被访问到。正如上面例子中我们在得到`sayHello()`的返回值之后又调用了`say2()`一样。需要注意到,我们调用的代码中引用了函数`sayHello2()`中的局部变量`text`。

```javascript
function(){console.log(text);} //say2.toString()的输出结果;
```
观察`say2.toString()`的输出结果,我们会发现代码指向变量`text`。这个匿名函数能够引用值为`Hello Bob`的变量`text`是因为`sayHello2()`的局部变量被保留在了闭包中。

在JavaScript中神奇的地方在于引用一个函数的同时会有一个秘密的引用指向在这个函数内部创建的闭包,类似于委托一个方法指针加一个隐藏的对象引用。

# 更多例子

当你读到很多关于闭包的文章时,总会感觉一头雾水,但是当你看到一些应用的例子时,你就能清晰的理解闭包是如何工作的了。下面是我推荐的一些例子,希望大家能够认真研究直到真正清楚闭包是如何工作的。如果在你没有完全理解的情况下就开始使用闭包,你很快就会成为很多奇怪bug的创造者。

下面这个例子展示了局部变量不是被复制,而是被保留在了引用当中。这是当外部函数存在的情况下将栈帧保存在内存中的方法之一。

```javascript
function say667(){
//处于闭包中的局部变量
var num=42;
var say=function(){console.log(num);}
num++;
return say;
}
var sayNumber=say667();
sayNumber();//logs 43
```

下面例子中的三个全局函数有对同一个闭包的共同引用,因为他们都在`setupSomeGlobals()`中被定义。

```javascript
var gLogNumber, gIncreaseNumber, gSetNumber;
function setupSomeGlobals() {
//处于闭包中的局部变量
var num = 42;
// 用全局变量存储对函数的引用
gLogNumber = function() { console.log(num); }
gIncreaseNumber = function() { num++; }
gSetNumber = function(x) { num = x; }
}
setupSomeGlobals();
gIncreaseNumber();
gLogNumber(); // 43
gSetNumber(5);
gLogNumber(); // 5

var oldLog = gLogNumber;

setupSomeGlobals();
gLogNumber(); // 42

oldLog() // 5
```
当这三个函数被创建时,它们能够共享对同一个闭包的访问-即对`setupSomeGlobals()`中的局部变量的访问。

需要注意到在上述例子中,如果你再次调用`setupSomeGlobals()`,会创建一个新的闭包。`gLogNumber()`、`gSetNumber()`和`gLogNumber()`会被带有新闭包的函数重写(在JavaScript中,当在一个函数中定义另外一个函数时,重新调用外部函数会导致内部函数被重新创建)。

下面这个例子对很多人来说都难以理解,所以你更需要真正理解它。在循环中定义函数时要格外小心:闭包中的局部变量或许不会和你的预想的一样。

```javascript
function buildList(list) {
var result = [];
for (var i = 0; i

JavaScript闭包,只学这篇就够了的更多相关文章

  1. JavaScript闭包只学这篇就够了

    闭包不是魔法 这篇文章使用一些简单的代码例子来解释JavaScript闭包的概念,即使新手也可以轻松参透闭包的含义. 其实只要理解了核心概念,闭包并不是那么的难于理解.但是,网上充斥了太多学术性的文章 ...

  2. 高效解决「SQLite」数据库并发访问安全问题,只这一篇就够了

    Concurrent database access 本文译自:https://dmytrodanylyk.com/articles/concurrent-database/ 对于 Android D ...

  3. 【javascript闭包】转载一篇不错的解释,也有几个大牛的链接

    初学闭包时一直以为很简单.但伴随对一个问题深入学习后,才算真正理解了闭包,同时也发现连<<JavaScript高级程序设计>>中都些不准确的地方. 我不准备从头介绍闭包的概念, ...

  4. Java多线程超级详解(只看这篇就够了)

    多线程能够提升程序性能,也属于高薪必能核心技术栈,本篇会全面详解Java多线程.@mikechen 主要包含如下几点: 基本概念 很多人都对其中的一些概念不够明确,如同步.并发等等,让我们先建立一个数 ...

  5. 学Redis这篇就够了!

    学Redis这篇就够了!   作者:王爷科技 https://www.toutiao.com/i6713520017595433485 Redis 简介 & 优势 Redis 数据类型 发布订 ...

  6. rodert教你学FFmpeg实战这一篇就够了

    rodert教你学FFmpeg实战这一篇就够了 建议收藏,以备查阅 pdf阅读版: 链接:https://pan.baidu.com/s/11kIaq5V6A_pFX3yVoTUvzA 提取码:jav ...

  7. Pycharm新手教程,只需要看这篇就够了

    pycharm是一款高效的python IDE工具,它非常强大,且可以跨平台,是新手首选工具!下面我给第一次使用这款软件的朋友做一个简单的使用教程,希望能给你带来帮助! 目前pycharm一共有两个版 ...

  8. windows server 2019 域控批量新增不用,只看这一篇就够了,别的不用看

    windows server 2019 域控批量新增不用,只看这一篇就够了,别的不用看 1. 新建excel表格 A B C D E 姓 名 全名 登录名 密码 李 四 李四 李四 test123!@ ...

  9. 一篇文章图文并茂地带你轻松学完 JavaScript 闭包

    JavaScript 闭包 为了更好地理解 JavaScript 闭包,笔者将先从 JavaScript 执行上下文以及 JavaScript 作用域开始写起,如果读者对这方面已经了解了,可以直接跳过 ...

随机推荐

  1. cssradius

    <!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8" ...

  2. 简单总结几种常见web攻击手段及其防御方式

    web攻击手段有几种,本文简单介绍几种常见的攻击手段及其防御方式 XSS(跨站脚本攻击) CSRF(跨站请求伪造) SQL注入 DDOS XSS 概念 全称是跨站脚本攻击(Cross Site Scr ...

  3. BufferedWriterTest

    public class BufferedWriterTest { public static void main(String[] args) { try { //创建一个FileWriter 对象 ...

  4. Maven下从HDFS文件系统读取文件内容

    需要注意以下几点 1.所以的包都是org.apache.hadoop.XXX 2.三个配置文件要放到指定文件夹中等待文件系统读取(src/main/resources):core-site.xml h ...

  5. gulp-rev-append静态资源添加版本号后缀,清理缓存

    大多用的是gulp-rev.gulp-rev-collerctor两个插件,但过程有点麻烦,使用gulp-rev-append插件轻松搞定 github:   https://github.com/b ...

  6. java 一款可以与ssm框架完美整合的web报表控件

    硕正套件运行于客户端(浏览器),与应用服务器(Application Server)技术无关,所以能完全用于J2EE. ASP.Net.php等技术开发的Web应用产品中. 硕正套件部署于服务器,支持 ...

  7. Webi Report 展示 图片链接 (Image Link)

    最近由于项目需求,在生成的Webi Report中需要增加一列展示相关数据系统的图片链接,要求用户可以通过点击图片链接展示图片. 方法如下: 1,首先找到系统中图片,查看随机一张图片的属性,找到图片的 ...

  8. UDP和多线程服务器

    UDP: UDP是数据报文传输协议,这个传输协议比较野蛮,发送端不需要理会接收端是否存在,直接就发送数据,不会像TCP协议一样建立连接.如果接收端不存在的话,发送的数据就会丢失,UDP协议不会去理会数 ...

  9. Sqoop Java API 导入应用案例

    环境信息: Linux+JDK1.7 Sqoop 1.4.6-cdh5.5.2 hadoop-core 2.6.0-mr1-cdh5.5.2 hadoop-common 2.6.0-cdh5.5.2 ...

  10. (转)Centos7 Nginx安装

    场景:工作中使用的suse,因为系统可可查资料太少,且系统中一些功能的确实,导致很多集群中功能无法顺利测试通过,在Centos上面进行测试,能够更快的熟悉项目的架构过程! 1 安装准备 首先由于ngi ...