# 闭包不是魔法
这篇文章使用一些简单的代码例子来解释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. OpenCV学习3-----利用鼠标、键盘回调函数实现标定人体关节点

    最近做实验,需要一些人体关节点的ground truth,需要自己手动标定,于是尝试使用OpenCV的鼠标键盘回调函数实现. 期间遇到不少问题,记录一下. 首先就是鼠标回调函数注册, namedWin ...

  2. 【LeetCode】219. Contains Duplicate II

    题目: Given an array of integers and an integer k, find out whether there are two distinct indices i a ...

  3. 【Android Developers Training】 105. 显示一个位置地址

    注:本文翻译自Google官方的Android Developers Training文档,译者技术一般,由于喜爱安卓而产生了翻译的念头,纯属个人兴趣爱好. 原文链接:http://developer ...

  4. Example002定时打开窗口

    <!--实例002定时打开窗口--> <script> // 3秒后弹出窗口: function time() { window.open("index.html&q ...

  5. Jenkins构建Android项目持续集成之单元测试及代码覆盖率

    单元测试 在软件开发中一直在推崇TDD(测试驱动开发),但是一直不能被有效的执行或者并不是真正的测试驱动开发(先开发后写单元测试),因为我们懒!而Android开发又是大多应用层面的开发,很多都是和视 ...

  6. github 项目管理

    一.在GitHub上创建项目 1. 打开浏览器 在地址栏输入地址:github.com 填写用户名.邮箱.密码 点击Sign up即可简单地注册 2. 完成注册,进入github平台, 点击new r ...

  7. CentOS-7.2安装Ambari-2.4.2

    一,Ambari 是什么? Ambari 跟 Hadoop 等开源软件一样,也是 Apache Software Foundation 中的一个项目,并且是顶级项目.目前最新的发布版本是 2.4.2. ...

  8. Linux 如何显示一个文件的某几行

    1. 从第1000行开始,显示2000行.即显示1000~2999行 cat input_file | tail -n +1000 | head -n 2000 2. 显示 1000行到3000行 c ...

  9. .Net 调用微信公众号扫一扫

    1.绑定域名 去微信公众号平台中设置js接口安全域名,要注意的是不填写http://, 只填写域名即可,如 www.baidu.com. 一个月只能修改三次,要谨慎填写. 2.引入JS文件 在页面中引 ...

  10. Collections 的 synchronized XXX方法

    摘要 static <T> Collection<T> synchronizedCollection(Collection<T> c) 返回指定 collectio ...