我也谈javascript闭包的原理理解
参考原文:http://www.oschina.net/question/28_41112
前言:还是一篇入门文章。Javascript中有几个非常重要的语言特性——对象、原型继承、闭包。其中闭包 对于那些使用传统静态语言C/C++的程序员来说是一个新的语言特性。本文将以例子入手来介绍Javascript闭包的语言特性,并结合一点 ECMAScript语言规范来使读者可以更深入的理解闭包。
注:本文是入门文章,例子素材整理于网络,如果你是高手,欢迎针对文章提出技术性建议和意见。本文讨论的是Javascript,不想做语言对比,如果您对Javascript天生不适,请自行绕道。
什么是闭包
闭包是什么?闭包是Closure,这是静态语言所不具有的一个新特性。但是闭包也不是什么复杂到不可理解的东西,简而言之,闭包就是:
- 闭包就是函数的局部变量集合,只是这些局部变量在函数返回后会继续存在。
- 闭包就是就是函数的“堆栈”在函数返回后并不释放,我们也可以理解为这些函数堆栈并不在栈上分配而是在堆上分配
- 当在一个函数内定义另外一个函数就会产生闭包
上面的第二定义是第一个补充说明,抽取第一个定义的主谓宾——闭包是函数的‘局部变量’集合。只是这个局部变量是可以在函数返回后被访问。(这个不是官方定义,但是这个定义应该更有利于你理解闭包)
做为局部变量都可以被函数内的代码访问,这个和静态语言是没有差别。闭包的差别在于局部变变量可以在函数执行结束后仍然被函数外的代码访问。这意味 着函数必须返回一个指向闭包的“引用”,或将这个”引用”赋值给某个外部变量,才能保证闭包中局部变量被外部代码访问。当然包含这个引用的实体应该是一个 对象,因为在Javascript中除了基本类型剩下的就都是对象了。可惜的是,ECMAScript并没有提供相关的成员和方法来访问闭包中的局部变 量。但是在ECMAScript中,函数对象中定义的内部函数() inner function是可以直接访问外部函数的局部变量,通过这种机制,我们就可以以如下的方式完成对闭包的访问了。
|
1
2
3
4
5
6
7
|
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:闭包中局部变量是引用而非拷贝
|
1
2
3
4
5
6
7
8
9
10
|
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:多个函数绑定同一个闭包,因为他们定义在同一个函数内。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
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; }}setupSomeGolbals(); // 为三个全局变量赋值gAlertNumber(); //666gIncreaseNumber();gAlertNumber(); // 667gSetNumber(12);//gAlertNumber();//12 |
例子3:当在一个循环中赋值函数时,这些函数将绑定同样的闭包
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
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:外部函数所有局部变量都在闭包内,即使这个变量声明在内部函数定义之后。
|
1
2
3
4
5
6
7
8
|
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:每次函数调用的时候创建一个新的闭包
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
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 单件:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
var singleton = function () { var privateVariable; function privateFunction(x) { ...privateVariable... } return { firstMethod: function (a, b) { ...privateVariable... }, secondMethod: function (c) { ...privateFunction()... } };}(); |
这个单件通过闭包来实现。通过闭包完成了私有的成员和方法的封装。匿名主函数返回一个对象。对象包含了两个方法,方法1可以方法私有变量,方法2访 问内部私有函数。需要注意的地方是匿名主函数结束的地方的’()’,如果没有这个’()’就不能产生单件。因为匿名函数只能返回了唯一的对象,而且不能被 其他地方调用。这个就是利用闭包产生单件的方法。
我也谈javascript闭包的原理理解的更多相关文章
- JavaScript闭包的一些理解
原文:JavaScript闭包的一些理解 简单一点的说:闭包就是能够读取其他函数内部变量的函数.那如何实现读取其它函数内部变量呢,大家都知道在JavaScript中内部函数可以访问其父函数中的变量,那 ...
- JavaScript闭包的深入理解
闭包算是javascript中一个比较难理解的概念,想要深入理解闭包的原理,首先需要搞清楚其他几个概念: 一.栈内存和堆内存 学过C/C++的同学可能知道,计算机系统将内存分为栈和堆两部分(大学的基础 ...
- 对JavaScript闭包和原型理解
最近在学js脚本的一些东西觉得里面有2个知识点比较难理解所以做了如下总结. 1.闭包 简单的理解:一个函数a ,内部有个函数b,那么这个函数b当被作为a函数的返回值得时候被外部的全局变量引用了,那么这 ...
- javascript 闭包最简单理解
首先说3点与闭包有关系的东西. 一.变量的作用域 变量的作用域不难理解. 1.函数内部可以访问函数外部的变量,而函数外部不能访问函数内部的变量. 2.如果在函数内定义变量的时候,不加var,那么是全局 ...
- JavaScript 闭包(个人理解)
当function里嵌套function时,内部的function可以访问外部function里的变量.但这不是闭包 function foo(x) { var tmp = 3; function b ...
- 浅谈JavaScript DDOS 攻击原理与防御
前言 DDoS(又名"分布式拒绝服务")攻击历史由来已久,但却被黑客广泛应用.我们可以这样定义典型的DDoS攻击:攻击者指使大量主机向服务器发送数据,直到超出处理能力进而无暇处理正 ...
- 也谈JavaScript闭包
闭包对于很多JavaScript初学者来说,都是比较难以理解的一个概念.其实,闭包并不是那么难以掌握的,理解闭包,只需要学会3个的基本事实. 首先我们来看第一个事实,JavaScript允许当前函数引 ...
- 浅谈Javascript闭包
垃圾回收器 我个人把闭包抽象的称之为”阻止垃圾回收器的函数”或者”有权访问另一个函数内部变量的函数"(当然这个是我个人的理解方式,每个人可能会有不同的理解方式),为什么这样说?这样说还得说说 ...
- JavaScript闭包函数的理解
闭包就是一个函数能够访问其函数外部作用域中的变量,即在外面可以调用函数中的函数的变量,其实他就是将函数内外部连接起来的桥梁 闭包三大特点: 1. 函数嵌套函数 2. 内部函数可以访问外部函数的变量 3 ...
随机推荐
- HDU2202--最大三角形(凸包,枚举)
Problem Description 老师在计算几何这门课上给Eddy布置了一道题目,题目是这样的:给定二维的平面上n个不同的点,要求在这些点里寻找三个点,使他们构成的三角形拥有的面积最大.Eddy ...
- unknow type name 'off64_t'
或者 错误"error: 'off64_t' does not name a type" MinGW的bug,使用-std=c++11, 有可能出现, 修改{MinGW dir} ...
- QTP如何连接/查询数据库
在日常测试工作中经常会遇到有关于数据的测试,比如数据的正确性,完整性,测试人员造的测试数据预期与实际程序运行结果是否一致,如果这个时候数据量比较多的情况下,通过人工比对无疑是一项非常耗时,而且安全系数 ...
- 下载google play上的APP
googol搜索 download apk directly,然后即可看到
- ASP.NET—013:实现带控件的弹出层(弹出框)
http://blog.csdn.net/yysyangyangyangshan/article/details/38458169 在页面中用到弹出新页面的情况比较多的,一般来说都是使用JS方法sho ...
- data Mining with Weka: Trailer More Data Mining with Weka 用weka 进行数据挖掘 Weka 用weka 进行更多数据挖掘
https://www.youtube.com/user/WekaMOOC 大学公开课 视频教程 weka 入门教程 data Mining with Weka: Trailer More Dat ...
- centos 7 切换运行模式
如设置命令行级别方法: systemctl set-default multi-user.target 设置窗口级别方法: systemctl set-default graphical.target
- postgres-xl 集体搭建
pgxl 集群搭建 一 预备 1 下载安装解压源码 /opt/ curl -O http://files.postgres-xl.org/postgres-xl95r1beta1.tar.gz tar ...
- 不同版本的Api控件显示可能不同
同样的一个布局, compileSdkVersion 24(android7.0)和 compileSdkVersion 18(android4.3)得到的结果可能不一样: <FrameLayo ...
- 剑指offer 旋转数组
class Solution { public: int minNumberInRotateArray(vector<int> rotateArray) { //常规的遍历方法时间是O(N ...