注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

phperwuhan的博客

记载一个phper的历程!phperwuhan.blog.163.com

 
 
 

日志

 
 

学习Javascript闭包(Closure)  

2010-07-30 17:51:00|  分类: js |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

来源:http://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.html

包(closure)是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现。

下面就是我的学习笔记,对于Javascript初学者应该是很有用的。

一、变量的作用域

要理解闭包,首先必须理解Javascript特殊的变量作用域。

变量的作用域无非就是两种:全局变量和局部变量。

Javascript语言的特殊之处,就在于函数内部可以直接读取全局变量。

  var n=999;

  function f1(){
    alert(n);
  }

  f1(); // 999

另一方面,在函数外部自然无法读取函数内的局部变量。

  function f1(){
    var n=999;
  }

  alert(n); // error

这里有一个地方需要注意,函数内部声明变量的时候,一定要使用var命令。如果不用的话,你实际上声明了一个全局变量!

  function f1(){
    n=999;
  }

  f1();

  alert(n); // 999

二、如何从外部读取局部变量?

出于种种原因,我们有时候需要得到函数内的局部变量。但是,前面已经说过了,正常情况下,这是办不到的,只有通过变通方法才能实现。

那就是在函数的内部,再定义一个函数。

  function f1(){

    n=999;

    function f2(){
      alert(n); // 999
    }

  }

在上面的代码中,函数f2就被包括在函数f1内部,这时f1内部的所有局部变量,对f2都是可见的。但是反过来就不行,f2内部的局部变量,对f1就是不可见的。这就是Javascript语言特有的"链式作用域"结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。

既然f2可以读取f1中的局部变量,那么只要把f2作为返回值,我们不就可以在f1外部读取它的内部变量了吗!

  function f1(){

    n=999;

    function f2(){
      alert(n);
    }

    return f2;

  }

  var result=f1();

  result(); // 999

三、闭包的概念

上一节代码中的f2函数,就是闭包。

各种专业文献上的"闭包"(closure)定义非常抽象,很难看懂。我的理解是,闭包就是能够读取其他函数内部变量的函数。

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

所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。

四、闭包的用途

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

怎么来理解这句话呢?请看下面的代码。

  function f1(){

    var n=999;

    nAdd=function(){n+=1}

    function f2(){
      alert(n);
    }

    return f2;

  }

  var result=f1();

  result(); // 999

  nAdd();

  result(); // 1000

在这段代码中,result实际上就是闭包f2函数。它一共运行了两次,第一次的值是999,第二次的值是1000。这证明了,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。

为什么会这样呢?原因就在于f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。

这段代码中另一个值得注意的地方,就是"nAdd=function(){n+=1}"这一行,首先在nAdd前面没有使用var关键字,因此nAdd是一个全局变量,而不是局部变量。其次,nAdd的值是一个匿名函数(anonymous function),而这个匿名函数本身也是一个闭包,所以nAdd相当于是一个setter,可以在函数外部对函数内部的局部变量进行操作。

五、使用闭包的注意点

1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。

2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。

六、思考题

如果你能理解下面两段代码的运行结果,应该就算理解闭包的运行机制了。

代码片段一。

  var name = "The Window";

  var object = {
    name : "My Object",

    getNameFunc : function(){
      return function(){
        return this.name;
      };

    }

  };

  alert(object.getNameFunc()());


代码片段二。

  var name = "The Window";

  var object = {
    name : "My Object",

    getNameFunc : function(){
      var that = this;
      return function(){
        return that.name;
      };

    }

  };

  alert(object.getNameFunc()());

(完)

文档信息

功能链接

广告

留言(25条)

forex88 说:

讲的很清楚明了,连我都懂了,要是我们大学时的老师也能这么讲课。。。他们只会放幻灯片

明城 说:

这里有个 PPT 用于说明 JS 闭包,说明得很透彻: http://www.gracecode.com/archives/2385/

张昭 说:

呵呵,可以作为面试题了!

十三 说:

闭包个人感觉是一种描述函数内部的数据结构,来描述函数的运行上下文.Javascript编程精粹 这本书算是讲的比较好一点.

迷途小书童 说:

类是有行为的数据,闭包是有数据的行为。

tt 说:

阮兄:
有点疑问:
function f1(){

    n=999;

    function f2(){
      alert(n);
    }

    return f2;

  }

  var result=f1();

  result(); // 999
可以写成如下的不也一样么?

function f1(){

    n=999;

    return n;

  }
var result=f1();
alert(result);

明城 说:

@tt 实际上后种方法每次调用 f1 时,都会声明 n = 999,而且 n 无法保留状态值(严格按照你的代码,其实 n 为全局变量,我理解你的本意为 var n = 999;)。

而第一种 f1 实际上返回的是个匿名函数,这样 n 作用域被另外个 f2 函数作用域所使用,因此它会保留。n 不会被重复声明,且内容会被保存

ahwing 说:

这是我见过最简单易懂的闭包教程。

支持下。

博主的博客写的不错,简单易懂,东西涉及的很多方面我都有兴趣,看来是同道中人,^_^

星光 说:

一文中的!!!!!!!!!!!!
学习了!!

zhaorui 说:

想知道思考题的答案,
我以为是:My Object

steven 说:

顶楼主,我读了一些文章。不是特明白。
有个问题。
记得有人说。外面的函数是closure,
好像楼主说里面的函数是closure.

不知道到底哪个是?谢谢。

jkd___w 说:

楼主讲讲最后一个思考题,没明白

hou 说:

请版主讲一讲最后一个例子怎么回事,没有看明白

George Wing 说:

函数中的this一般是指向window中的变量。

引用hou的发言:
请版主讲一讲最后一个例子怎么回事,没有看明白

George Wing 说:

上面本人说得不太正确。
this的指向是由它所在函数调用的上下文决定的,而不是由它所在函数定义的上下文决定的。

George Wing 说:

如果非要指向object,可显式的控制--把代码的最后一句改为 alert(object.getName().call(object));

c-star 说:

阮大哥讲的很透彻 受益匪浅

ya 说:

大道至简,给予我这个初学者很大的帮助,谢谢!

过客 说:

浅显易懂,很好。

如下看法,认为有待商榷:
#1、有一个地方需要注意,函数内部声明变量的时候,一定要使用var命令。如果不用的话,你实际上声明了一个全局变量!

#2、这段代码中另一个值得注意的地方,就是“nAdd=function(){n+=1}”这一行,首先在nAdd前面没有使用var关键字,因此nAdd是一个全局变量,而不是局部变量。其次,nAdd的值是一个匿名函数(anonymous function),而这个匿名函数本身也是一个闭包,所以nAdd相当于是一个setter,可以在函数外部对函数内部的局部变量进行操作。

  function f1(){
test = 10;
    var n=999;
    nAdd=function(){n+=1}
    function f2(){
      alert(n);
    }
    return f2;
  }
//如果 #1 说法正确,下句会打印10,实际结果是test未定义。
//alert(test); // error test 未定义
 
//如果 #2 正确,语句 nAdd(); 位置在何处应该都能执行,测试结果在下面这个位置,也就是语句 var result=f1(); 前。是不能执行的。
//nAdd();
var result=f1();
result(); // 999
  nAdd();
result(); // 1000

ning 说:

To 过客:

函数内部定义的方法和变量,要等到函数执行过以后,才会真正定义

Jason 说:

但是从过客说的里面可以引出另外的问题,当使用这样的代码时。

  function f1(){
test = 10;
    var n=999;
    nAdd=function(){n+=1}
    function f2(){
      alert(n);
    }
    return f2;
  }

如果在函数f1定义之前添加变量定义

var n = 1;
然后调用
f1()();
则显示为999。说明nAdd中的n确实是作为全局变量存在。于是问题就来了——有什么方法让他可以是父函数中定义的n呢?

大道至简,很不错!~ 这篇文章我要转了...

iworm 说:

引用George Wing的发言:

函数中的this一般是指向window中的变量。

this关键字代表的实例会根据环境不同而变化的. 他总是指向owner 看看这篇你大概就动this这个关键字了

http://www.quirksmode.org/js/this.html

tomwang 说:

最后一个题感觉和闭包没什么关系啊,能详细解释一下吗?因为当一个函数作为函数而不是方法来调用的时候,this指向的是全局对象,这在《Javascript权威指南》上说的很清楚,所以答案肯定是“The Window”,和闭包没什么关系啊

  评论这张
 
阅读(434)| 评论(0)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017