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 相关文章推荐
Extjs学习笔记之五 一个小细节renderTo和applyTo的区别
Jan 07 Javascript
也说JavaScript中String类的replace函数
Sep 22 Javascript
根据身份证号自动输出相关信息(籍贯,出身日期,性别)
Nov 15 Javascript
JavaScript常用基础知识强化学习
Dec 09 Javascript
JavaScript中匿名函数的用法及优缺点详解
Jun 01 Javascript
angularjs中使用ng-bind-html和ng-include的实例
Apr 28 Javascript
js断点调试心得分享(必看篇)
Dec 08 Javascript
解决vue2.0 element-ui中el-upload的before-upload方法返回false时submit()不生效问题
Aug 24 Javascript
跟混乱的页面弹窗说再见
Apr 11 Javascript
vue实现从外部修改组件内部的变量的值
Jul 30 Javascript
vue+iview使用树形控件的具体使用
Nov 02 Javascript
详解vue 组件的实现原理
Nov 12 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
php 之 没有mysql支持时的替代方案
2006/10/09 PHP
基于PHP CURL获取邮箱地址的详解
2013/06/03 PHP
php smarty模板引擎的6个小技巧
2014/04/24 PHP
PHP结合Mysql数据库实现留言板功能
2016/03/04 PHP
Yii2中简单的场景使用介绍
2017/06/02 PHP
Laravel框架分页实现方法分析
2018/06/12 PHP
JS定时器实例详细分析
2013/10/11 Javascript
jQuery不使用插件及swf实现无刷新文件上传
2014/12/08 Javascript
jQuery中width()方法用法实例
2014/12/24 Javascript
Javascript原型链的原理详解
2016/01/05 Javascript
jquery计算出left和top,让一个div水平垂直居中的简单实例
2016/07/13 Javascript
jquery ajax后台返回list,前台用jquery遍历list的实现
2016/10/30 Javascript
自己封装的一个简单的倒计时功能实例
2016/11/23 Javascript
JavaScript中 this 指向问题深度解析
2017/02/21 Javascript
收藏AngularJS中最重要的核心功能
2017/07/09 Javascript
史上最全JavaScript常用的简写技巧(推荐)
2017/08/17 Javascript
JS实现的新闻列表自动滚动效果示例
2019/01/30 Javascript
Vue 监听元素前后变化值实例
2020/07/29 Javascript
原生JavaScript实现幻灯片效果
2021/02/19 Javascript
[05:05]DOTA2亚洲邀请赛 战队出场仪式
2015/02/07 DOTA
python中wx将图标显示在右下角的脚本代码
2013/03/08 Python
Python实现基于权重的随机数2种方法
2015/04/28 Python
Python使用设计模式中的责任链模式与迭代器模式的示例
2016/03/02 Python
python 编程之twisted详解及简单实例
2017/01/28 Python
Python正则表达式教程之二:捕获篇
2017/03/02 Python
PyQt4实现下拉菜单可供选择并打印出来
2018/04/20 Python
详解Python3 对象组合zip()和回退方式*zip
2019/05/15 Python
Python urlopen()和urlretrieve()用法解析
2020/01/07 Python
PyTorch实现ResNet50、ResNet101和ResNet152示例
2020/01/14 Python
Tensorflow 使用pb文件保存(恢复)模型计算图和参数实例详解
2020/02/11 Python
python 多线程中join()的作用
2020/10/29 Python
结合CSS3的新特性来总结垂直居中的实现方法
2016/05/30 HTML / CSS
文秘档案管理岗位职责
2014/03/06 职场文书
二手房买卖协议书
2014/04/10 职场文书
家长对学生的评语
2014/04/18 职场文书
干货:我将这样书写我的演讲稿!
2019/05/09 职场文书