本文内容

  • 闭包
  • 闭包和引用
  • 参考资料

闭包是 JavaScript 的重要特性,非常强大,可用于执行复杂的计算,可并不容易理解,尤其是对之前从事面向对象编程的人来说,对 JavaScript 认识和编程显得更难。特别是在看一些开源的 JavaScript 代码时感觉尤其如此,跟看天书没什么区别。

一般情况下,人们认识一个事物,会根据之前的经验,进行对比和总结,在脑中建立一个模型,从而理解掌握它,但是 JavaScript 与面向对象编程实在“没有可比性”,最明显的是某过于写法,总觉得“怪怪的”,更不用说,其一些高级特性。如果说“对象”在面向对象编程时的出现相当有规律,但是在 JavaScript 中则毫无规律,无处不在,甚至在你意想不到的地方。

首先看两段代码。

示例 1:

var sMessage = "hello world";

 

function sayHelloWorld() {

    alert(sMessage);

}

 

sayHelloWorld();

示例 2:

var iBaseNum = 10;

 

function addNum(iNum1, iNum2) {

    function doAdd() {

        return iNum1 + iNum2 + iBaseNum;

    }

    return doAdd();

}

示例 1 和示例 2 都是闭包,只是 2 比 1 复杂,甚至还有更复杂的写法,比如返回多个闭包。

示例 1,脚本被载入内存后,并没有为函数 sayHelloWorld() 计算变量 sMessage 的值。该函数捕获 sMessage 的值只是为了以后的使用,也就是说,解释程序知道在调用该函数时要检查 sMessage 的值。sMessage 将在函数调用 sayHelloWorld() 时(最后一行)被赋值,显示消息 "hello world"。

示例 2,函数 addNum() 包括函数 doAdd() (闭包)。内部函数是一个闭包,因为它将获取外部函数的参数 iNum1 和 iNum2 以及全局变量 iBaseNum 的值。 addNum() 的最后一步调用了 doAdd(),把两个参数和全局变量相加,并返回它们的和。

这里要掌握的重要概念是,doAdd() 函数根本不接受参数,它使用的值是从执行环境中获取的。

闭包


闭包,根据 ECMAScript 描述,词法(lexically)表示包括不被计算的变量的函数,函数可以使用函数之外定义的变量,它意味着当前作用域总能够访问外部作用域中的变量。函数是 JavaScript 中唯一拥有自身作用域的结构,因此闭包的创建依赖于函数。函数内部的函数访问其所在函数的变量(局部变量、形参),这些变量会受到内部函数的影响,当其外部函数外被调用时,就会形成闭包。内部的函数会在其外部函数返回后,被执行。

示例 3:

function foo() {

    var a = 10;

 

    function bar() {

        a *= 2;

        return a;

    }

    return bar; // 返回内部函数 bar

}

 

var baz = foo();

baz(); // 20

说明:

  • foo 是 bar 的外部函数,bar 是 foo 的内部函数;a 是 foo 的局部变量;
  • bar 访问 foo 的局部变量 a;
  • foo 返回 bar。
  • bar 在 foo 的外部被调用。

当执行 baz() 后,闭包使 Javascript 垃圾回收机制不会回收 foo 所占的资源。因为,baz 实际指向 foo 的内部函数 bar,bar 依赖 foo 的局部变量 a。这样,在执行 var baz=foo() 后,baz 实际指向了 bar,而不是 foo。bar 访问了 foo 的局部变量 a,当执行 baz() 后,a 为 20。这就形成了一个闭包。如下图所示:

图 1

如果把 foo 看作是一个包,根据剪头指示,形成了一个闭包。结果是局部变量 a 的持久性(如示例 4 所示)。下面代码就不是闭包。无论执行多少次,都是显示 20。

示例 4:

function foo() {

    var a = 10;

    function bar() {

        alert(a *= 2);

    }

    bar();

}

foo(); // 20

foo(); // 20

foo(); // 20

从以上两个示例看,闭包有点类似于面向对象的接口和委托,——只是调用方法而无需知道具体细节。

示例 5:

function foo() {

    var a = 10;

    function bar() {

        a *= 2;

        return a;

    }

    return bar;

}

 

var baz = foo();

baz(); // 20

baz(); // 40

baz(); // 80

 

var blat = foo();

blat(); // 20

 

闭包和引用


模拟私有变量

代码 6:

function Counter(start) {

    var count = start;

    return {

        increment: function () {

            count++;

        },

 

        get: function () {

            return count;

        }

    }

}

 

var foo = Counter(4);

foo.increment();

foo.get(); // 5

这里,Counter 函数返回两个闭包,函数 increment 和函数 get。 这两个函数都维持着 对外部作用域 Counter 的引用,因此总可以访问此作用域内定义的变量 count.

为什么不能在外部访问私有变量

因为 JavaScript 中不可以对作用域进行引用或赋值,因此没有办法在外部访问 count 变量,唯一的途径就是通过上面那两个闭包。

var foo = new Counter(4);

foo.hack = function() {

    count = 1337;

};

上面的代码不会改变定义在 Counter 作用域中的 count 变量的值,因为 foo.hack 没有 定义在那个作用域内。它将会创建或者覆盖全局变量 count。

循环中的闭包

一个常见的错误出现在循环中使用闭包,假设我们需要在每次循环中调用循环序号,

for(var i = 0; i < 10; i++) {

    setTimeout(function() {

        console.log(i);  

    }, 1000);

}

上面的代码不会输出数字 0 到 9,而是会输出数字 10 十次。

当 console.log 被调用的时候,匿名函数保持对外部变量 i 的引用,此时for循环已经结束, i 的值被修改成了 10.

为了得到想要的结果,需要在每次循环中创建变量 i 的拷贝。

避免引用错误

为了正确的获得循环序号,最好使用 匿名包裹器(自执行匿名函数)。

for(var i = 0; i < 10; i++) {

    (function(e) {

        setTimeout(function() {

            console.log(e);  

        }, 1000);

    })(i);

}

外部的匿名函数会立即执行,并把 i 作为它的参数,此时函数内 e 变量就拥有了 i 的一个拷贝。

当传递给 setTimeout 的匿名函数执行时,它就拥有了对 e 的引用,而这个值是不会被循环改变的。

有另一个方法完成同样的工作;那就是从匿名包装器中返回一个函数。这和上面的代码效果一样。

for(var i = 0; i < 10; i++) {

    setTimeout((function(e) {

        return function() {

            console.log(e);

        }

    })(i), 1000)

}

 

参考资料

 

下载 Demo

Javascript 闭包(Closures)的更多相关文章

  1. [译]Javascript中的闭包(closures)

    本文翻译youtube上的up主kudvenkat的javascript tutorial播放单 源地址在此: https://www.youtube.com/watch?v=PMsVM7rjupU& ...

  2. JavaScript闭包的底层运行机制

    转自:http://blog.leapoahead.com/2015/09/15/js-closure/ 我研究JavaScript闭包(closure)已经有一段时间了.我之前只是学会了如何使用它们 ...

  3. 什么是JavaScript闭包终极全解之一——基础概念

    本文转自:http://www.cnblogs.com/richaaaard/p/4755021.html 什么是JavaScript闭包终极全解之一——基础概念 “闭包是JavaScript的一大谜 ...

  4. 闭包(Closures)

    浅析 JavaScript 中的闭包(Closures) 一.前言 对于 JavaScript 来说,闭包是一个非常强大的特征.但对于刚开始接触的初学者来说它又似乎是特别高深的.今天我们一起来揭开闭包 ...

  5. JavaScript闭包的一些理解

    原文:JavaScript闭包的一些理解 简单一点的说:闭包就是能够读取其他函数内部变量的函数.那如何实现读取其它函数内部变量呢,大家都知道在JavaScript中内部函数可以访问其父函数中的变量,那 ...

  6. 我也谈javascript闭包的原理理解

    参考原文:http://www.oschina.net/question/28_41112 前言:还是一篇入门文章.Javascript中有几个非常重要的语言特性——对象.原型继承.闭包.其中闭包 对 ...

  7. Javascript闭包入门(译文)

    前言 总括 :这篇文章使用有效的javascript代码向程序员们解释了闭包,大牛和功能型程序员请自行忽略. 译者 :文章写在2006年,可直到翻译的21小时之前作者还在完善这篇文章,在Stackov ...

  8. 解密JavaScript闭包

    译者按: 从最简单的计数器开始,按照需求对代码一步步优化,我们可以领会闭包的神奇之处. 原文: Closures are not magic 译者: Fundebug 为了保证可读性,本文采用意译而非 ...

  9. JavaScript闭包其一:闭包概论 函数式编程中一些基本定义

    http://www.nowamagic.net/librarys/veda/detail/1707前面介绍了作用域链和变量对象,现在再讲闭包就容易理解了.闭包其实大家都已经谈烂了.尽管如此,这里还是 ...

  10. 探究JavaScript闭包

    什么是JavaScript闭包? 刚开始知道这个词,就误以为是自动执行的匿名函数块. 比如说+function(){}(); 然而并不是,那么请看下面的例子: function init() { va ...

随机推荐

  1. Panorama和Pivot控件

    Windows Phone提供了Panorama和Pivot这两种控件供用户横向切换导航的方式来显示具有内容比较相关的页面.本文主要对这两个控件进行描述,包括如何使用,以及一些最佳实践. 其中包括如下 ...

  2. ios 获得通讯录中联系人的所有属性 亲测,可行 兼容io6 和 ios 7

    //获取通讯录中的所有属性,并存储在 textView 中,已检验,切实可行.兼容io6 和 ios 7 ,而且ios7还没有权限确认提示. -(void)getAddressBook { ABAdd ...

  3. JAVA使用Marvin在图片中搜索图片

    Java对图像的处理框架比较少,目前比较流行的有Jmagick以及Marvin,但Jmagick只能处理图像(上篇Java清除图片中的恶意信息(利用Jmagick)中对Jmagick已做过简略介绍), ...

  4. Android性能检测工具——traceview

    之前的几篇文章中介绍了android中常用的一些工具,今天介绍的工具也是比较实用和方便的,它可以用量化的指标告诉我们哪个方法执行的时间最长,被调用的次数最多,有没有重复调用.下面我们就来看看它是怎么为 ...

  5. 深入理解Java Callable接口

    概述Callable和Runnbale一样代表着任务,区别在于Callable有返回值并且可以抛出异常.其使用如下: public class CallableDemo { static class ...

  6. 【转】Mysql行转换为列

    From : http://www.cnblogs.com/lhj588/archive/2012/06/15/2550392.html# 今晚需要统计数据生成简易报表,由原表格数据是单行的形式,最好 ...

  7. idea自动生成serialVersionUID , serialVersionUID的作用

    Java的序列化的机制通过判断serialVersionUID来验证版本的一致性.在反序列化的时候与本地的类的serialVersionUID进行比较,一致则可以进行反序列化,不一致则会抛出异常Inv ...

  8. Python Configparser模块读取、写入配置文件

    写代码中需要用到读取配置,最近在写python,记录一下. 如下,假设有这样的配置. [db] db_host=127.0.0.1 db_port=3306 db_user=root db_pass= ...

  9. go语言之进阶篇主协程先退出

    1.主协程先退出 示例: package main import ( "fmt" "time" ) //主协程退出了,其它子协程也要跟着退出 func main ...

  10. 遭遇sql server 2005 启动包未能正确加载需要重新安装错误,重装.NET FRAMEWORK经历分析

    开发的机器,系统情况如下: 1.server 2003 sp2 x86 2.补丁安装360 3.升级到IE8 因为担心server 2003 sp2 不能够自动update,最近都是用360打补丁,比 ...