当前位置:文档之家› javascript语言中函数闭包现象

javascript语言中函数闭包现象

浅析javascript语言中的函数闭包现象
摘要:闭包在很多javascript高级应用中都会出现。

本文主要以javascript语言为例分析闭包现象的形成,理解闭包的运行机制及使用。

关键词:闭包;内部变量;作用域
中图分类号:tp312.2 文献标识码:a 文章编号:1007-9599 (2012) 23-0000-02
闭包问题是英格兰brighton beers活动中提出来的。

闭包的概念很抽象,如果不用代码来说明,将很难用描述性语句把它解释清楚。

所以本文将以javascript语言为例解释说明什么是闭包,分析闭包的运行机制及使用注意事项。

1 闭包的概念
什么是闭包?在计算机科学中,闭包(closure)是词法闭包(lexical closure)的简称,是引用了自由变量的函数。

这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。

ecmascript允许使用内部函数--即函数定义和函数表达式位于另一个函数的函数体内。

[1]而且,这些内部函数可以访问它们所在的外部函数中声明的所有局部变量、参数和声明的其他内部函数。

当其中一个这样的内部函数在包含它们的外部函数之外被调用时,就会形成闭包。

所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。

由于在javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包
简单理解成“定义在一个函数内部的函数”。

2 理解闭包在javascript中的运行及使用
2.1 如何理解闭包
闭包的创建相对容易,有时创建闭包编程人员根本没有意识到这是闭包,尤其是在ie等常见的浏览器环境下运行下,所编写的程序在很大程度上存在潜在的问题。

因此在编写javascript高级应用程序是,对闭包的使用和它的运行机制必须有一定了解。

而了解闭包运行机制的就要先解析在编写过程中的环境变量及作用域。

javascript中每个函数都是一个函数对象(函数实例),既然是对象,就有相关的属性和方法。

[scope]就是每个函数对象都具有的一个仅供javascript引擎内部使用的属性,该属性是一个集合(类似于链表结构),集合中保存了该函数在被创建时的作用域中的所有对象,而这个作用域集合形成的链表则被称为scopechain (作用域链)[2]。

该作用域链中保存的作用域对象,就是该函数可以访问的所有数据。

例如定义一个函数求两个数的和运算
当add函数被创建时,函数所在的全局作用域的全局对象被放置到add函数的作用域链([[scope]]属性)中。

从图2-1中看到作用域链的第一个对象保存的是全局对象,全局对象中保存了诸如this,window,document以及全局对象中的add函数。

这也就是为什么可以在在全局作用域下的函数中访问window(this),访问全局变量,访问函数自身的原因。

当局部变量和全局变量同名时,会使用局部变量而不使用全局变量,
2.2 全局变量在javascript函数中的应用
分析javascript特殊的变量作用域是可以方便编程人员理解闭包。

在编程语言中,所有的变量的作用域从函数的角度来说都可以分成:全局变量和局部变量。

而javascript语言的是比较特殊的一种语言,它可以从函数内部可以直接读取并引用全局变量。

一个闭包就是一个引用了其被生成的环境中、所属的变量范围内的所有变量的函数。

[3]例如定义了一个函数f1,代码如下:
var x = 1;
function f1()
{var y = 1;
var result = x + y;
document.wirte(“result=”, result);
};
这里我们首先定义了一个变量“x”,值为1。

然后我们定义无参无返回值函数f1,在f1函数内部result中使用了“x”变量。

这个变量是被f1 引用了,自动被添加到了f1的运行环境中了。

当我们执行f1时,它会输出了一个预期的结果2。

但函数f1执行时,原始的“x”此时已经不在是它当初的变量环境,但它仍然能被使用。

2.3 局部变量在javascript函数中的应用
在javascript语言中,函数内部的子函数可以直接读取局部变量,所以闭包也可以说成是“嵌套定义在一个函数内部的函数”。

而闭包就成为了函数内部和函数外部相联系一个媒介。

因此,有时候当需要得到函数内的局部变量。

可以在函数的内部,再定义一个函数。

例如定义了一个函数f2代码如下:
function f2()
{n=1;
function f3()
{var s=n+2
alert(“s=”+ s);//s的结果=3 }
return f3;
}
在代码中,函数f3就被定义在函数f2内部,这时函数f2内部的所有局部变量,对函数f3都是有效的。

即n可以被f3函数引用,但是f3内部的局部变量,对f2就是不可见的。

即s变量在f2函数中不可见。

这就是javascript语言中的“chain scope”,计算机专业术语称为“链式作用域”,在链式作用域中子对象会逐层向上寻找所有父对象的变量。

所以,父对象的所有变量,对子对象都是有效的,子对象可以随意使用父对象的变量,但是子对象中的变量却屏蔽了,父对象没法使用它的变量。

在代码运行中,函数f3可以使用f2中的局部变量n,但父对象函数f2想要读取f3的内部变量怎么办?这里只要把f3作为返回值,则函数f2即可读取到内部变量s的值。

这里的f3()就是一个闭包。

2.4 闭包在javascript语言中的应用
闭包可以用在许多地方。

它的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。

例如定义一个函数a,在函数a内部定义如下函数b,代码如下:
function a() //外层函数a
{ var n=1; //临时变量n
function b()//内层函数b
{
alert(n++);//引用外层临时变量n
}
return b; }//返回函数b
var result=a (); //调用函数a,函数b被引用
result(); //函数b中n变量被引用值为2.
}
在这段代码中,函数b实际上就是一个闭包函数。

在程序代码中函数b被调用了两次,变量n值第一次的n=1,第二次的n=2。

这说明了,函数a中的局部变量n一直驻留在内存中,当函数a调用后没有被释放,第二次调用时仍然再使用。

这种情况说明,函数b 可以引用函数a的全局变量,在运行中函数b始终在内存中,而函数b的存在依赖于函数a,在调用结束后,函数a也会驻留在内存,不会被javascript的垃圾回收机制回收。

从上面的例子我们得出以下在javascript中使用闭包是的结论:(1)使用闭包可以使函数内的变量更加安全,防止其他变量通过任何其他路径访问该变量。

例如上例,变量n只有函数b才能访问,而其他函数想要访问函数a中的n变量时不可能的。

(2)使用闭包可以使变量在内存中驻留。

方便函数的调用和使用该变量。

依然如上例,由于该代码创建了一个闭包,当每次执行result()时,n都会被累加。

(3)使用闭包可以封装js私有属性和私有方法,使函数内部的变量不会被外部访问。

3 闭包应用中的注意事项
(1)合理利用和创建闭包。

闭包会使函数中的变量长久保存内存中,消耗内存的资源,造成网页崩溃等性能问题,从而导致ie
中内存泄露等。

所以在编写该代码运行后,要将不使用的局部变量删除。

(2)闭包很轻易的可以改变父函数内部变量值,所以谨慎使用闭包。

在把闭包当作父函数的公有方法或把内部变量当作父函数的私有属性时,父函数内的变量值不能随意改变,造成函数变量值的混乱。

(3)警惕和及时察觉意外闭包现象。

前面的例子也说明了闭包很容易创建,在javascript中允许使用闭包,所以在没有认识到闭包是一种语言特性的javascript时,编程人员会按照想法来使用内部函数。

但对使用内部函数的结果并不明了,有可能在定义函
数时已经定义了一个闭包而没有意识到。

会使最终结果并不是编程人员向要的结果。

在ie的内存泄漏问题中,闭包意外创建的会存在很多潜在的技术问题,从而影响到代码的性能及结果。

4 结束语
闭包问题不在javascript语言于是否允许使用闭包。

而在于在理解了闭包的运行机制基础上,谨慎有效的使用闭包,才能写出更为安全的代码,为编写程序提供方便。

参考文献:
[1]david flanagan.javascript权威指南(第5版)[m].李强.北京:机械工业出版社,2007.
[2]nicholas c.zakas.javascript高级程序设计(第3版)[m].李松峰,曹力.北京:人民邮电出版社,2012.
[3]张云帆.javascript闭包技术及ie内存泄漏分析[j].电脑知识与技术,2008,35.
[4]邓绪高.javascript中变量作用域浅析[j].信息与电脑(理论版),2010.12/
[作者简介]徐红梅(1977.10-),硕士学位,四川职业技术学院计算机系讲师。

相关主题