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

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. python3.5无法安装pip,报错ImportError: cannot import name 'HTTPSHandler'

    本人系统为:centos6 解决方法: 1  安装openssl yum install openssl 2  安装openssl-devel yum install openssl-devel 3  ...

  2. jvm系列(三):GC算法 垃圾收集器

    原文出处:纯洁的微笑 这篇文件将给大家介绍GC都有哪几种算法,以及JVM都有那些垃圾回收器,它们的工作原理. 概述 垃圾收集 Garbage Collection 通常被称为"GC" ...

  3. yum安装_yum命令的相关操作

    2017年1月11日, 星期三 yum安装的四种方式 一.默认:从国外下载 二.国内:从阿里获取  http://mirrors.aliyun.com 1. cd /etc/yum.repos.d 2 ...

  4. Spring Data JPA原生SQL查询

    package com.test.cms.dao.repository;import org.springframework.stereotype.Repository;import javax.pe ...

  5. shell学习1---基本的shell命令

    基本脚本 反引号: ``  反引号里面的内容是命令行,通过反引号用户可以将shell命令的输出赋给变量,比如: test=`date +%y%m%d` echo "The time is : ...

  6. 线搜索(line search)方法

    在机器学习中, 通常需要求某个函数的最值(比如最大似然中需要求的似然的最大值). 线搜索(line search)是求得一个函数\(f(x)\)的最值的两种常用迭代方法之一(另外一个是trust re ...

  7. Richard Stallman:让我们关注和尊敬自由软件教父

    1953年,Richard Stallman生于美国纽约曼哈顿区.在度过了并不快乐的童年之后,他在哈佛大学找到了自己的家.在MIT人工智能实验室工作期间,展露出了自己的计算 机天赋.对他来说,开发操作 ...

  8. python学习之argparse模块的使用

    以下内容主要来自:http://wiki.jikexueyuan.com/project/explore-python/Standard-Modules/argparse.html argparse ...

  9. KL散度(Kullback–Leibler divergence)

    KL散度是度量两个分布之间差异的函数.在各种变分方法中,都有它的身影. 转自:https://zhuanlan.zhihu.com/p/22464760 一维高斯分布的KL散度 多维高斯分布的KL散度 ...

  10. from setuptools import setup ImportError: No module named setuptools【转】

    转自:http://www.cnblogs.com/chinacloud/archive/2010/12/24/1915644.html from setuptools import setupImp ...