许多人第一次接触闭包大概都是从高程里这段代码开始的:

function createFunctions() {
var result = new Array(); for(var i=0; i<10; i++) {
result[i] = function() {
return i;
}
}
return result;
}
var foo = createFunction();

或者是用for循环在给网页中一连串元素绑定例如onclick事件时。

所有的教材在讲到这一点时都会给出这样的解释: 因为每个函数都保存着createFunction中的活动对象,所以它们引用的都是同一个变量 i 。而循环结束后 i 的值为10,所以每个函数的输出都是10.

解释非常简洁与正确。

然而还是会有一部分人看了这个解释后一知半解,比如我。

我第一次看到这个解释后有了这么一连串疑问: 虽然知道 i 最终是 10,但是在每次赋值过程中 i 并不是 10 啊,为什么非要取最后一个值呢?i 并不是引用数据类型,为什么可以说“它们引用的都是同一个变量 i ?

如果你和我一样有这个疑问,其实对这个问题而言我们不理解的地方并不是闭包,但是这个问题被打上了一个严重的”闭包“标签,导致很长一段时间里我都以为自己不了解闭包。

实际上,我不理解的并不是闭包这个概念,而是更为基础的,函数调用的时机。

我们把代码中赋值的哪一段改一下:

result[i] = function() {
return j;
}

把 i 改成 j, 一个并没有定义的变量。

如果我们仅仅把改完之后的代码贴到console里运行,它是不会报错的。因为虽然createFunctions被调用了,却并未调用赋给result的函数。

只有继续使用语句调用result中的某个元素:

result[0](1);

这样才会抛出 undefined 错误。

这说明了一个问题:仅仅声明某一个函数,引擎并不会对函数内部的任何变量进行查找或赋值操作。只会对函数内部的语法错误进行检查(如果往内部函数加上非法语句,那么不用调用也会报错)。

所以开头问题里的循环语句:

for(var i=0; i<10; i++)
result[i] = function()
return i;

我原本以为它是这样的:

 result[0] = function() { return 0; };
result[1] = function() { return 1; };
result[2] = function() { return 2; };

实际上它是这样的:

 result[0] = function() { return i; };
result[1] = function() { return i; };
result[2] = function() { return i; };

数组里的 i 和 函数里的 i 并不是一回事, 外面的是常量, 里面的是变量。

而当我们调用result[0]函数时, 这个函数执行到 return 语句,发现并没有 i 这个变量,于是顺着作用链去找,在createFunctions里找到了已经变成10的 i ,于是输出 10. 这个过程才是闭包的寻找变量的过程。

根据这个思路寻找解决方案时思路就明确多了,只要在每次赋值过程中,不让 i 作为变量,而是确确实实地利用当时 i 的值,方法就是将 i 作为函数参数进行调用:

result[i] = (function(val) { return val; })(i);

这样一来在每一次赋值的过程中,每一个result[i]都与 i 的当前值产生了联系。

当然,这样修改的问题在于,原题返回的是一个函数,这里返回的却是一个值。

所以还要把返回值改成相应的函数:

 result[i] = (function (val) {
return function () {
return val;
};
})(i);

这样相当于给目标函数套上了一层块级作用域,并且在 i 每次循环时都将它的值赋给了这个块级作用域中的一个临时变量。这个临时变量其实和 i 没有太大区别,只不过 i 在它的作用域声明时值为 0 ,结束后变成了10.而对每个临时变量而言,开始是多少,结束还是多少。

进一步谈闭包

任何声明在另一个函数内部的函数都可以称为闭包。也就是说,闭包是一个函数。不过也有些地方会讲闭包是内部函数以及其作用域链组成的一个整体。两种说法其实一个意思,毕竟严格来说,函数的作用域也是函数的一部分。不过我更喜欢后面一种说法,因为它强调了闭包的重点:维持作用域。

闭包主要有两个概念:可以访问外部函数,维持函数作用域。第一个概念并没有什么特别,大部分编程语言都有这个特性,内部函数可以访问其外部变量这种事情很常见。所以重点在于第二点。举例如下:

var globalValue;

function out() {
var value = 1;
function inner() {
return value;
}
globalValue = inner;
} out(); globalValue() // return 1;

我们先不考虑闭包地看一下这个问题:首先声明了一个全局变量,然后调用了out函数,调用函数的过程中全局变量被赋值了一个函数。out函数调用结束之后,按照内存处理机制,它内部的所有变量应该都被释放掉了,不过还好我们把inner复制给了全局变量,所以还可以在外部调用它。接下来我们调用了全局变量,这时候因为out内部作用域已经被释放了,所以应该找不到value的值,返回应该是undefined。

但是事实是,它的确返回了 1,即内部变量。本该已经消失了,只能存在于out函数内部的变量,走到了墙外。这就是闭包的强大之处。

JavaScript闭包与变量的经典问题的更多相关文章

  1. Javascript 闭包与变量

    1.闭包与变量 JavaScript中的作用域链的机制引出了一个副作用,即闭包只能取得包含函数中任何变量的最后一个值.闭包所保存的是整个变量对象,而不是某个特殊的值. 1 2 3 4 5 6 7 8 ...

  2. 那些年,我们误解的 JavaScript 闭包

    说到闭包,大部分的初始者,都是谈虎色变的.最近对闭包,有了自己的理解,就感觉.其实我们误解闭包.也被网上各种说的闭包的解释给搞迷糊. 一句话:要想理解一个东西还是看权威的东西. 下面我来通俗的讲解一个 ...

  3. 学习Javascript闭包(Closure)及几个经典面试题理解

    今天遇到一个面试题,结果让我百思不得其解.后来在查阅了各种文档后,理清了来龙去脉.让我们先来看看这道题: function Foo( ){ var i = 0; return function( ){ ...

  4. JavaScript 使用闭包防止变量污染

    javaScript在多人协作时,如果定义过多的全局变量 有可能造成全局变量命名冲突,使用闭包来解决功能对变量的调用 将变量写到一个独立的空间里面 就是闭包里面 var name = "外部 ...

  5. JavaScript 使用闭包保护变量 防止污染

    使用JavaScript编写插件或团队协作时,可使用闭包来解决此类以下两个问题: 1.定义过多全局变量,可能会造成全局变量命名冲突: 2.在插件内定义变量,需要保护该变量不被轻易修改: 优点:可以把局 ...

  6. javascript闭包和作用域链

    最近在学习前端知识,看到javascript闭包这里总是云里雾里.于是翻阅了好多资料记录下来本人对闭包的理解. 首先,什么是闭包?看了各位大牛的定义和描述各式各样,我个人认为最容易一种说法: 外部函数 ...

  7. 【转】深入理解JavaScript闭包闭包(closure) (closure)

    一.什么是闭包?"官方"的解释是:闭包是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分.相信很少有人能直接看懂这句话,因为他描述 ...

  8. javascript 闭包(转)

    一.变量的作用域 要理解闭包,首先必须理解Javascript特殊的变量作用域. 变量的作用域无非就是两种:全局变量和局部变量. Javascript语言的特殊之处,就在于函数内部可以直接读取全局变量 ...

  9. 深入理解Javascript闭包 新手版

    一.什么是闭包?  “官方”的解释是:所谓“闭包”,指的是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分. 相信很少有人能直接看懂这句话,因为他描述 ...

随机推荐

  1. php与Git下基于webhook的自动化部署

    前言 2018年第一篇文章,没啥技术含量,权当笔记 我们一般都会用git或者svn来管理我们的代码 每次代码更新后还要手动的去把服务器上的代码也更新一遍 项目小了还好 项目大了着实浪费时间 要是服务器 ...

  2. Java基础-集合的嵌套

    Java基础-集合的嵌套 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.静态导入 静态导入是在JDK1.5后的新特性,可以减少开发的代码量,但是实际用处是很一般,静态导入的标准 ...

  3. HTTP返回代码 403 404 500等代表的含义

    在网站日志中,我们经常会看到很多返回的http代码,如201.304.404.500等等.可是这些具体的返回的HTTP代码究竟什么含义呢,在此做一下知识普及吧,记不住不要紧,到时候看看就行了,但最主要 ...

  4. 转:UIViewController中各方法调用顺序及功能详解

    UIViewController中loadView, viewDidLoad, viewWillUnload, viewDidUnload, viewWillAppear, viewDidAppear ...

  5. 半小时让你成为EXCEL高手

  6. -webkit-css

    WebKit CSS: 1.“盒模型”的具体描述性质的包围盒块内容,包括边界,填充等等. .test{ -webkit-border-bottom-left-radius: radius; -webk ...

  7. Enumeration和Iterator

    首先,Enumeration已经被Iterator取代了..... Enumeration是个接口,不是类,使用时需要具体的实现类. 里面只定义了两个方法: hasMoreElements()和nex ...

  8. [转载]js 程序执行与顺序实现详解

    http://www.jb51.net/article/36755.htm JavaScript是一种描述型脚本语言,由浏览器进行动态的解析与执行,浏览器对于不同的方式有不同的解析顺序,详细介绍如下, ...

  9. 【leetcode 简单】 第八十八题 猜数字大小

    我们正在玩一个猜数字游戏. 游戏规则如下: 我从 1 到 n 选择一个数字. 你需要猜我选择了哪个数字. 每次你猜错了,我会告诉你这个数字是大了还是小了. 你调用一个预先定义好的接口 guess(in ...

  10. 天梯赛 L2-013. (并查集) 红色警报

    题目链接 题目描述 战争中保持各个城市间的连通性非常重要.本题要求你编写一个报警程序,当失去一个城市导致国家被分裂为多个无法连通的区域时,就发出红色警报.注意:若该国本来就不完全连通,是分裂的k个区域 ...