Javascript的闭包详解


Posted in Javascript onDecember 26, 2014

前言:还是一篇入门文章。Javascript中有几个非常重要的语言特性——对象、原型继承、闭包。其中闭包 对于那些使用传统静态语言C/C++的程序员来说是一个新的语言特性。本文将以例子入手来介绍Javascript闭包的语言特性,并结合一点 ECMAScript语言规范来使读者可以更深入的理解闭包。

注:本文是入门文章,例子素材整理于网络,如果你是高手,欢迎针对文章提出技术性建议和意见。本文讨论的是Javascript,不想做语言对比,如果您对Javascript天生不适,请自行绕道。

什么是闭包
闭包是什么?闭包是Closure,这是静态语言所不具有的一个新特性。但是闭包也不是什么复杂到不可理解的东西,简而言之,闭包就是:

闭包就是函数的局部变量集合,只是这些局部变量在函数返回后会继续存在。
闭包就是就是函数的“堆栈”在函数返回后并不释放,我们也可以理解为这些函数堆栈并不在栈上分配而是在堆上分配
当在一个函数内定义另外一个函数就会产生闭包
上面的第二定义是第一个补充说明,抽取第一个定义的主谓宾——闭包是函数的‘局部变量'集合。只是这个局部变量是可以在函数返回后被访问。(这个不是官方定义,但是这个定义应该更有利于你理解闭包)

做为局部变量都可以被函数内的代码访问,这个和静态语言是没有差别。闭包的差别在于局部变变量可以在函数执行结束后仍然被函数外的代码访问。这意味 着函数必须返回一个指向闭包的“引用”,或将这个”引用”赋值给某个外部变量,才能保证闭包中局部变量被外部代码访问。当然包含这个引用的实体应该是一个 对象,因为在Javascript中除了基本类型剩下的就都是对象了。可惜的是,ECMAScript并没有提供相关的成员和方法来访问闭包中的局部变 量。但是在ECMAScript中,函数对象中定义的内部函数(inner function)是可以直接访问外部函数的局部变量,通过这种机制,我们就可以以如下的方式完成对闭包的访问了。

function greeting(name) {

    var text = 'Hello ' + name; // local variable

    // 每次调用时,产生闭包,并返回内部函数对象给调用者

    return function() { alert(text); }

}

var sayHello=greeting("Closure");

sayHello()  // 通过闭包访问到了局部变量text

上述代码的执行结果是:Hello Closure,因为sayHello()函数在greeting函数执行完毕后,仍然可以访问到了定义在其之内的局部变量text。

好了,这个就是传说中闭包的效果,闭包在Javascript中有多种应用场景和模式,比如Singleton,Power Constructor等这些Javascript模式都离不开对闭包的使用。

ECMAScript闭包模型
ECMAScript到底是如何实现闭包的呢?想深入了解的亲们可以获取ECMAScript 规范进行研究,我这里也只做一个简单的讲解,内容也是来自于网络。

在ECMAscript的脚本的函数运行时,每个函数关联都有一个执行上下文场景(Execution Context) ,这个执行上下文场景中包含三个部分

文法环境(The LexicalEnvironment)
变量环境(The VariableEnvironment)
this绑定
其中第三点this绑定与闭包无关,不在本文中讨论。文法环境中用于解析函数执行过程使用到的变量标识符。我们可以将文法环境想象成一个对象,该对 象包含了两个重要组件,环境记录(Enviroment Recode),和外部引用(指针)。环境记录包含包含了函数内部声明的局部变量和参数变量,外部引用指向了外部函数对象的上下文执行场景。全局的上下文 场景中此引用值为NULL。这样的数据结构就构成了一个单向的链表,每个引用都指向外层的上下文场景。

例如上面我们例子的闭包模型应该是这样,sayHello函数在最下层,上层是函数greeting,最外层是全局场景。如下图:  因此当sayHello被调用的时候,sayHello会通过上下文场景找到局部变量text的值,因此在屏幕的对话框中显示出”Hello Closure” 变量环境(The VariableEnvironment)和文法环境的作用基本相似,具体的区别请参看ECMAScript的规范文档。

闭包的样列
前面的我大致了解了Javascript闭包是什么,闭包在Javascript是怎么实现的。下面我们通过针对一些例子来帮助大家更加深入的理解闭包,下面共有5个样例,例子来自于JavaScript Closures For Dummies(镜像)。 例子1:闭包中局部变量是引用而非拷贝

function say667() {

    // Local variable that ends up within closure

    var num = 666;

    var sayAlert = function() { alert(num); }

    num++;

    return sayAlert;

}

 

var sayAlert = say667();

sayAlert()

因此执行结果应该弹出的667而非666。

例子2:多个函数绑定同一个闭包,因为他们定义在同一个函数内。

function setupSomeGlobals() {

    // Local variable that ends up within closure

    var num = 666;

    // Store some references to functions as global variables

    gAlertNumber = function() { alert(num); }

    gIncreaseNumber = function() { num++; }

    gSetNumber = function(x) { num = x; }

}

setupSomeGlobals(); // 为三个全局变量赋值

gAlertNumber(); //666

gIncreaseNumber();

gAlertNumber(); // 667

gSetNumber(12);//

gAlertNumber();//12

例子3:当在一个循环中赋值函数时,这些函数将绑定同样的闭包

function buildList(list) {

    var result = [];

    for (var i = 0; i < list.length; i++) {

        var item = 'item' + list[i];

        result.push( function() {alert(item + ' ' + list[i])} );

    }

    return result;

}

function testList() {

    var fnlist = buildList([1,2,3]);

    // using j only to help prevent confusion - could use i

    for (var j = 0; j < fnlist.length; j++) {

        fnlist[j]();

    }

}

testList的执行结果是弹出item3 undefined窗口三次,因为这三个函数绑定了同一个闭包,而且item的值为最后计算的结果,但是当i跳出循环时i值为4,所以list[4]的结果为undefined.

例子4:外部函数所有局部变量都在闭包内,即使这个变量声明在内部函数定义之后。

function sayAlice() {

    var sayAlert = function() { alert(alice); }

    // Local variable that ends up within closure

    var alice = 'Hello Alice';

    return sayAlert;

}

var helloAlice=sayAlice();

helloAlice();

执行结果是弹出”Hello Alice”的窗口。即使局部变量声明在函数sayAlert之后,局部变量仍然可以被访问到。

例子5:每次函数调用的时候创建一个新的闭包

function newClosure(someNum, someRef) {

    // Local variables that end up within closure

    var num = someNum;

    var anArray = [1,2,3];

    var ref = someRef;

    return function(x) {

        num += x;

        anArray.push(num);

        alert('num: ' + num +

        '\nanArray ' + anArray.toString() +

        '\nref.someVar ' + ref.someVar);

    }

}

closure1=newClosure(40,{someVar:'closure 1'});

closure2=newClosure(1000,{someVar:'closure 2'});

 

closure1(5); // num:45 anArray[1,2,3,45] ref:'someVar closure1'

closure2(-10);// num:990 anArray[1,2,3,990] ref:'someVar closure2'

闭包的应用
Singleton 单件:

var singleton = function () {

    var privateVariable;

    function privateFunction(x) {

        ...privateVariable...

    }

 

    return {

        firstMethod: function (a, b) {

            ...privateVariable...

        },

        secondMethod: function (c) {

            ...privateFunction()...

        }

    };

}();

这个单件通过闭包来实现。通过闭包完成了私有的成员和方法的封装。匿名主函数返回一个对象。对象包含了两个方法,方法1可以方法私有变量,方法2访 问内部私有函数。需要注意的地方是匿名主函数结束的地方的'()',如果没有这个'()'就不能产生单件。因为匿名函数只能返回了唯一的对象,而且不能被 其他地方调用。这个就是利用闭包产生单件的方法。

Javascript 相关文章推荐
JavaScript函数的4种调用方法详解
Apr 22 Javascript
推荐6款基于jQuery实现图片效果插件
Dec 07 Javascript
jQuery插件bgStretcher.js实现全屏背景特效
Jun 05 Javascript
chrome调试javascript详解
Oct 21 Javascript
Bootstrap每天必学之媒体对象
Nov 30 Javascript
jQuery解决input元素的blur事件和其他非表单元素的click事件冲突问题
Aug 15 Javascript
EditPlus中的正则表达式 实战(2)
Dec 15 Javascript
原生js实现可爱糖果数字时间特效
Dec 30 Javascript
原生js实现放大镜效果
Jan 11 Javascript
jQuery实现的自定义轮播图功能详解
Dec 28 jQuery
微信小程序实现轨迹回放的示例代码
Dec 13 Javascript
node.js如何自定义实现一个EventEmitter
Jul 16 Javascript
jQuery中focus事件用法实例
Dec 26 #Javascript
生成二维码方法汇总
Dec 26 #Javascript
Javascript中的五种数据类型详解
Dec 26 #Javascript
javascript工厂方式定义对象
Dec 26 #Javascript
jQuery中click事件用法实例
Dec 26 #Javascript
javascript实现类似超链接的效果
Dec 26 #Javascript
浅析javascript操作 cookie对象
Dec 26 #Javascript
You might like
老机欣赏|中国60年代精品收音机
2021/03/02 无线电
php调用mysql数据 dbclass类
2011/05/07 PHP
有关PHP性能优化的介绍
2013/06/20 PHP
php数组去重实例及分析
2013/11/26 PHP
基础的WordPress插件制作教程
2015/11/24 PHP
老生常谈php 正则中的i,m,s,x,e分别表示什么
2017/03/02 PHP
通过源码解析Laravel的依赖注入
2018/01/22 PHP
突发奇想的一个jquery插件
2010/11/19 Javascript
基于jquery实现的上传图片及图片大小验证、图片预览效果代码
2011/04/12 Javascript
js采用map取到id集合组并且实现点击一行选中一行
2013/12/16 Javascript
js的隐含参数(arguments,callee,caller)使用方法
2014/01/28 Javascript
javascript为按钮注册回车事件(设置默认按钮)的方法
2015/05/09 Javascript
bootstrap-wysiwyg结合ajax实现图片上传实时刷新功能
2016/05/27 Javascript
JS传值出现中文参数乱码的解决方法
2016/06/30 Javascript
js实时获取窗口大小变化的实例代码
2016/11/18 Javascript
jQuery.Form实现Ajax上传文件同时设置headers的方法
2017/06/26 jQuery
你可能不知道的JSON.stringify()详解
2017/08/17 Javascript
Angular6封装http请求的步骤详解
2018/08/13 Javascript
React通过redux-persist持久化数据存储的方法示例
2019/02/14 Javascript
vue 插件的方法代码详解
2019/06/06 Javascript
js实现简单掷骰子效果
2019/10/24 Javascript
[48:05]2018DOTA2亚洲邀请赛 3.31 小组赛 B组 VGJ.T vs VP
2018/03/31 DOTA
[02:17]快乐加倍!DOTA2食人魔魔法师至宝+迎霜节活动上线
2019/12/22 DOTA
[01:04:02]DOTA2-DPC中国联赛 正赛 Elephant vs IG BO3 第二场 1月24日
2021/03/11 DOTA
在Python3中使用asyncio库进行快速数据抓取的教程
2015/04/02 Python
Python Queue模块详细介绍及实例
2016/12/27 Python
numpy中索引和切片详解
2017/12/15 Python
Python开发的十个小贴士和技巧及长常犯错误
2018/09/27 Python
python爬取淘宝商品销量信息
2018/11/16 Python
Python上下文管理器用法及实例解析
2019/11/11 Python
python如何通过闭包实现计算器的功能
2020/02/22 Python
python在一个范围内取随机数的简单实例
2020/08/16 Python
详解canvas.toDataURL()报错的解决方案全都在这了
2020/03/31 HTML / CSS
健康监测猫砂:Pretty Litter
2017/05/25 全球购物
创业项目大全(适合在家创业的项目)
2019/08/15 职场文书
Spring Bean是如何初始化的详解
2022/03/22 Java/Android