javascript—闭包

我们先来思考一个问题:怎么读取一个函数里的私有变量?

众所周知,javascript具有函数作用域,也即是说函数内部可以读取外部的变量,但外部是无法读取函数内部的私有变量的。因此如果我们在这个函数(暂且叫做outer)里再添加一个内部函数innerinner就能读取到outer的私有变量。如果我们再把inner当作返回值返回,不就相当于间接得到了outer的私有变量了嘛!而事实上,这就是闭包。

官方对闭包的解释是:一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。闭包的特点: 
  1.作为一个函数变量的一个引用,当函数返回时,其处于激活状态。 
  2.一个闭包就是当一个函数返回时,一个没有释放资源的栈区。

这样的解释有点抽象,从狭义上来讲其实可以理解为 “一个函数引用了另一个函数作用域里的变量”。

我们来看下面的例子:


  1. function closure(){
  2. var name = "enm";
  3. return {
  4. getStr:function(){
  5. return name;
  6. }
  7. }
  8. }
  9. var builder = new closure();
  10. builder.name;//undefined
  11. name;//undefined,这里是访问不了function的私有变量的
  12. console.log(builder.getStr()); //返回了enm

上面构造了一个闭包,这个闭包都维持着对外部作用域的引用,因此不管在哪调用总是能够访问函数中的变量。在一个函数内部定义的函数,会将外部函数的活跃对象添加到自己的作用域链中,因此上面实例中通过内部函数能够访问外部函数的属性,这也是javascript模拟私有变量的一种方式。

闭包经典问题

我们来看看下面经典的例子,相信任何一位初学者在接触闭包时都会遇到这个问题:


  1. function timeManage() {
  2. for (var i = 0; i < 5; i++) {
  3. setTimeout(function() {
  4. console.log(i);
  5. },1000)
  6. };
  7. }
  8.  

上面的程序并没有按照我们预期的输入1-5的数字,而是5次全部输出了5。我们先来看解决办法:


  1. function timeManage() {
  2. for (var i = 0; i < 5; i++) {
  3. (function(num) {
  4. setTimeout(function() {
  5. console.log(num);
  6. }, 1000);
  7. })(i);
  8. }
  9. }

或者在闭包匿名函数中再返回一个匿名函数赋值:


  1. function timeManage() {
  2. for (var i = 0; i < 10; i++) {
  3. setTimeout((function(e) {
  4. return function() {
  5. console.log(e);
  6. }
  7. })(i), 1000)
  8. }
  9. }
  10. //timeManager();输出1,2,3,4,5

我再尝试另一种方法,我把它叫做“消除延迟方法”:


  1. function timeManage() {
  2. function foo() {
  3. console.log(i);
  4. }
  5. for (var i = 0; i < 5; i++) {
  6. foo()
  7. };
  8. }
  9. timeManage();//输出1,2,3,4,5

继续看另一个例子


  1. function createClosure(){
  2. var result = [];
  3. for (var i = 0; i < 5; i++) {
  4. result[i] = function(){
  5. return i;
  6. }
  7. }
  8. return result;
  9. }

调用createClosure()[0]()返回的是5,createClosure()[4]()返回值仍然是5。原因我们最后再说,因为现在我自己的理解跟网上其他人的理解有出入。我们先来看看怎么解决。


  1.  
  2. function createClosure() {
  3. var result = [];
  4. for (var i = 0; i < 5; i++) {
  5. result[i] = function(num) {
  6. return function() {
  7. console.log(num);
  8. }
  9. }(i);
  10. }
  11. return result;
  12. }
  13. //createClosure()[1]()输出1;createClosure()[2]()输出2

再使用我自己的“消除延迟”方法看看


  1. function createClosure(){
  2. var result = [];
  3. for (var i = 0; i < 5; i++) {
  4. result[i] = (function(){
  5. return i;
  6. })();
  7. }
  8. return result;
  9. }
  10. var w = new createClosure();
  11. w; //成功输出[0,1,2,3,4]

以上提供的解决办法,无论是匿名包裹器还是通过嵌套匿名函数的方式,原理上都是将变量i的值复制给实参num,在匿名函数的内部又创建了一个用于返回num的匿名函数,这样每个函数都有了一个num的副本,互不影响了。而第三种方法消除延迟则是先定义函数,或者定义立即执行函数,然后在for循环中直接调用函数,这也是一个闭包,也依然保持对外部变量i的访问,但是不会出现我们说的问题


  1. function createClosure(){
  2. var result = [];
  3. for (var i = 0; i < 5; i++) {
  4. result[i] = function(){
  5. return i;
  6. };
  7. }
  8. return result;
  9. }
  10. var w = new createClosure();
  11. w; //输出 [object Array][function, function, function, function, function]
  12. w[0](); //5

这里就很明显了,我们把函数表达式直接赋值给result[i],此时函数并没有执行,看看我们控制台输出的结果就知道了,w只是一个包含了函数表达式的Array,而不是具体数值。而等到我们调用w[0]()的时候,函数才开始执行计算,此时再调用外部的变量i(循环已完毕,i
== 5),所以w[0]()为5。到此,该问题就解决了。

事实上,这个例子的本意是用来说明闭包保持对外部函数变量的引用(reference),而不是复制值(copy)。因此当外部变量改变时,内部函数的引用也会跟着改变。但是,这个例子很容易让人迷糊,让初学者摸不着头脑,以为闭包非常的magic,以为是闭包的奇异功能导致了这个奇怪现象的发生。所以,有人解释出现这个问题的原因是由于作用域链机制的影响,闭包只能取得内部函数的最后一个值,这样说没有错,因为你执行完循环再调用函数,函数引用同一个i,那么结果肯定是最后修改的值5。但这样解释会让这个问题变得迷糊,特别是对初学者而言,他们会混乱,会觉得闭包很深奥很不可理解。因为其实本质上这个例子是通过setTimeout函数故意延迟了函数的执行,通过我第三种解决方法可以看出,假如每一次循环都能立即执行函数,那么是完全可以输出每一个i的正确值的。也就是说,其实这个问题的容易让人迷糊的原因是函数没有立即执行,而不能说是闭包产生的结果。

闭包中的this

闭包中的this 在闭包中使用this时要特别注意,稍微不慎可能会引起问题。通常我们理解this对象是运行时基于函数绑定的,全局函数中this对象就是window对象,而当函数作为对象中的一个方法调用时,this等于这个对象(TODO 关于this做一次整理)。由于匿名函数的作用域是全局性的,因此闭包的this通常指向全局对象window:


  1. var scope = "global";
  2. var object = {
  3. scope:"local",
  4. getScope:function(){
  5. return function(){
  6. return this.scope;
  7. }
  8. }
  9. }

调用object.getScope()()返回值为global而不是我们预期的local,前面我们说过闭包中内部匿名函数会携带外部函数的作用域,那为什么没有取得外部函数的this呢?每个函数在被调用时,都会自动创建this和arguments,内部匿名函数在查找时,搜索到活跃对象中存在我们想要的变量,因此停止向外部函数中的查找,也就永远不可能直接访问外部函数中的变量了。总之,在闭包中函数作为某个对象的方法调用时,要特别注意,该方法内部匿名函数的this指向的是全局变量。 幸运的是我们可以很简单的解决这个问题,只需要把外部函数作用域的this存放到一个闭包能访问的变量里面即可:


  1. var scope = "global";
  2. var object = {
  3. scope:"local",
  4. getScope:function(){
  5. var that = this;
  6. return function(){
  7. return that.scope;
  8. }
  9. }
  10. }

object.getScope()()返回值为local。

内存与性能 由于闭包中包含与函数运行期上下文相同的作用域链引用,因此,会产生一定的负面作用,当函数中活跃对象和运行期上下文销毁时,由于必要仍存在对活跃对象的引用,导致活跃对象无法销毁,这意味着闭包比普通函数占用更多的内存空间,在IE浏览器下还可能会导致内存泄漏的问题,如下:


  1. function bindEvent(){
  2. var target = document.getElementById("elem");
  3. target.onclick = function(){
  4. console.log(target.name);
  5. }
  6. }

上面例子中匿名函数对外部对象target产生一个引用,只要是匿名函数存在,这个引用就不会消失,外部函数的target对象也不会被销毁,这就产生了一个循环引用。解决方案是通过创建target.name副本减少对外部变量的循环引用以及手动重置对象:


  1. function bindEvent(){
  2. var target = document.getElementById("elem");
  3. var name = target.name;
  4. target.onclick = function(){
  5. console.log(name);
  6. }
  7. target = null;
  8. }

闭包中如果存在对外部变量的访问,无疑增加了标识符的查找路径,在一定的情况下,这也会造成性能方面的损失。解决此类问题的办法我们前面也曾提到过:尽量将外部变量存入到局部变量中,减少作用域链的查找长度。

总结:闭包不是javascript独有的特性,但是在javascript中有其独特的表现形式,使用闭包我们可以在javascript中定义一些私有变量,甚至模仿出块级作用域,但闭包在使用过程中,存在的问题我们也需要了解,这样才能避免不必要问题的出现。

时间: 2024-08-04 08:14:07

javascript—闭包的相关文章

javascript闭包

前言 闭包对于初学者而言一直是一个不太好理解的概念.最近在学习javascript的时候碰巧看到了关于这方面的讲解,自己才明白了许多,所以把它写出来分享给大家.当然,本文也是参考了很多blog和书籍,加上自己的理解写出来的,文章末尾会附上对应的参考文档. 基础知识  变量作用域 //javascript的变量作用域可以分为两种:全局变量和局部变量. //在函数内声明的变量就是局部变量,这个变量在函数体内可访问,在函数外部无法直接读取局部变量. //例如: var globalVariable =

javascript闭包的高级使用方法实例

这篇文章介绍了javascript闭包的高级使用方法实例,有需要的朋友可以参考一下   扩展 Code: 复制代码 代码如下: var blogModule = (function (my) {  my.AddPhoto = function () { //添加内部代码  };  return my; }(blogModule)); Say: 将自身传进方法,然后实现了方法的扩展,有点象零件组装啊 Code: 复制代码 代码如下: var blogModule = (function (my)

基于javascript 闭包基础分享

如果对作用域,函数为独立的对象这样的基本概念理解较好的话,理解闭包的概念并在实际的编程实践中应用则颇有水到渠成之感. 在DOM的事件处理方面,大多数程序员甚至自己已经在使用闭包了而不自知,在这种情况下,对于浏览器中内嵌的JavaScript引擎的bug可能造成内存泄漏这一问题姑且不论,就是程序员自己调试也常常会一头雾水. 用 简单的语句来描述JavaScript中的闭包的概念:由于JavaScript中,函数是对象,对象是属性的集合,而属性的值又可以是对象,则在函数内 定义函数成为理所当然,如果

javascript闭包的理解

 1.首先我们要知道变量作用域链 变量的作用域分两种:全局变量和局部变量.没有定义到任何函数中的变量为全局变量,在函数中定义的变量为局部变量,注意在函数内部定义变量时一定要使用var关键字,不带var关键字的变量为全局变量. javascript中每一段代码都有与之关联的作用域链,这个作用域链是一个对象列表或者链表,定义了这段代码"作用域"中的变量.顶层代码的作用域由全局变量组成:不包含嵌套的函数的作用域链有两个对象:一个是定义的函数参数和局部变量的对象,一个是全局变量对象:而嵌套函数

javascript 闭包详解

 这篇文章主要详细介绍了javascript 闭包的相关资料,十分详尽,需要的朋友可以参考下     javascript 闭包是一个很有趣的东东.看了些相关资料http://www.jb51.net/article/29472.htm,对其印象最深刻的是:实现了public 和private. 创建一个非匿名闭包最简单的语法是:   代码如下: var obj = (function(){//各种代码 });   闭包最经典的例子:   代码如下: var makeCounter = (fun

关于javascript闭包的一点疑问

问题描述 关于javascript闭包的一点疑问 闭包存储局部变量的机理是什么?它如何存储的?比如如下一个闭包 function test(){ var num = 10; return function(){ num++; return num; }; }; var n = test(); alert(n()); //返回11 alert(n()); //返回12,实现了累加 alert(n()); //继续累加 第二次alert(n())通过闭包实现了累加,但是这个局部变量num是如何存储在

函数-关于javascript闭包的一点疑问

问题描述 关于javascript闭包的一点疑问 function create(){ var arr = new Array(); for (var i=0; i<10; i++){ arr[i] = function(num){ return function(){ return num; }; }(i);// (i)有是什么意思?} 解决方案 匿名函数的参数,可以这么理解 var f = function(num){ return function(){ return num; };};a

JavaScript 闭包环境很奇特 - 相当于类与实例的关系?!

JavaScript 闭包环境很奇特 - 相当于类与实例的关系?! 太阳火神的美丽人生 (http://blog.csdn.net/opengl_es) 本文遵循"署名-非商业用途-保持一致"创作公用协议 转载请保留此句:太阳火神的美丽人生 -  本博客专注于 敏捷开发及移动和物联设备研究:iOS.Android.Html5.Arduino.pcDuino,否则,出自本博客的文章拒绝转载或再转载,谢谢合作. 又一个疑问贴! 不过我相信,问题并不是难在如何解决,最终就是个能解决与不能解决

Javascript闭包简单理解

原文:Javascript闭包简单理解 提到闭包,想必大家都早有耳闻,下面说下我的简单理解.说实话平时工作中实际手动写闭包的场景并不多,但是项目中用到的第三方框架和组件或多或少用到了闭包.所以,了解闭包是非常必要的.呵呵... 一.什么是闭包简而言之,就是能够读取其他函数内部变量的函数.由于JS变量作用域的特性,外部不能访问内部变量,内部可以外部变量. 二.使用场景1. 实现私有成员.2. 保护命名空间,避免污染全局变量.3. 缓存变量. 先看一个封装的例子: var person = func

Javascript闭包的一些研究

原文:Javascript闭包的一些研究     本文不谈闭包的概念,因为概念容易把人搞晕,本文希望通过几个鲜活的例子来探究闭包的性质,相信对理解闭包会有所帮助.   程序1 var f = (function() { var n = 10; return function() { ++n; console.log(n); } })(); f(); 输出: 11 结论: 闭包函数可以访问外层函数中的变量.   程序2 var arr = []; (function() { var n = 0;