您的位置:首页 > Web前端 > JavaScript

学习Javascript闭包(Closure)

2016-03-29 15:28 295 查看
《javascript高级程序设计》(第三版)第7章第2节:

闭包是指有权访问另一个函数作用域中的变量函数。

《javascript权威指南》 (第六版)第8章第6节:

从技术的角度讲,所有的JavaScript函数都是闭包:它们都是对象,它们都关联到作用域链

或者看网上教程:

JavaScript 秘密花园

闭包是 JavaScript 一个非常重要的特性,这意味着当前作用域总是能够访问外部作用域中的变量。

我理解的闭包定义:闭包就是一个访问父函数局部变量的函数。当一个内部函数被其外部函数之外的变量引用时,就形成了一个闭包。
作用:1、读取函数内部的变量。2、让这些变量的值始终保持在内存中。有时候我们需要一个模块中定义这样一个变量:希望这个变量一直保存在内存中但又不会“污染”全局的变量,这个时候,我们就可以用闭包来定义这个模块。
如果你看了上述定义和理论一头雾水(几乎是肯定的),那么来看代码。

要理解闭包,首先必须理解Javascript特殊的变量作用域
变量的作用域无非就是两种:全局变量和局部变量。Javascript语言的特殊之处,就在于函数内部可以直接读取全局变量。

var n=999;
  function f1(){
    alert(n);
  }
  f1(); // 999</span>
另一方面,在函数外部自然无法读取函数内的局部变量。这里在alert(n);前面加f1();也是一样的。

function f1(){
    var n=999;
  }
  al
4000
ert(n); // error</span>
这里有一个地方需要注意,函数内部声明变量的时候,一定要使用var命令。如果不用的话,你实际上声明了一个全局变量!
function f1(){
    n=999;
  }
  f1();
  alert(n); // 999</span>


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

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

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

function f1(){
    var n=999;
    function f2(){
      alert(n); // 999
    }
  }


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

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

function f1(){
    var n=999;
    function f2(){
      alert(n);
    }
    return f2;
  }
  var result=f1();
  result(); // 999


f2函数,就是闭包。
重复一段上面的话,结合代码来理解:我理解的闭包定义:闭包就是一个访问父函数局部变量的函数。当一个内部函数被其外部函数之外的变量引用时,就形成了一个闭包。

示例代码1
function f1(){
    var n=999;
    nAdd=function(){n+=1}
    function f2(){
      alert(n);
    }
    return f2;
  }
  var result=f1();
  result(); // 999
  nAdd();
  result(); // 1000</span>
在这段代码中,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,可以在函数外部对函数内部的局部变量进行操作。

示例代码2

function A(){
var count = 0;
function B(){
count ++;
console.log(count);
}
return B;
}
var c = A();
c();// 1
c();// 2
c();// 3</span>


count是A中的一个变量,它的值在B中被改变,函数B每执行一次,count的值就在原来的基础上累加1。因此,A中的count一直保存在内存中。

举一个实际的例子;你有一个比价类型的应用,需要实时的获取t1,t2两个网站上商品的价格信息。你的核心代码大概是这样的。
//version 0.1
function query_t1(item)
{
var api_url_t1;
...part1
//根据item生成请求api地址, api_url_t1
$.get(api_url_t1, function(data)
{
var price
...part2
//解析data数据,提取price信息。
...part3
//使用price信息。将价格显示在t1对应的位置
})
}

function query_t2(item)
{
//和query_t2类似
}
</span>
如果只需要你取两个网站的数据,这样写还算ok。慢慢的,你发现在产品经理的要求下,你要获取网站越来越多,你不断的复制自己的代码 。代码中有大量重复的部分,维护也变得复杂。比如,你调整一个price的显示方式,所有query_t*都需要对应地方的修改。你开始厌倦,想减少那些重复的代码。

首先想到的就是优化part3,将显示的部分抽离成一个单独的函数
//version 0.2
function show_price(site,price)
{
...
//使用price信息。将价格显示在site对应的位置
}

function query_t1(item)
{
var api_url_t1;
...part1
//根据item生成请求api地址, api_url_t1
$.get(api_url_t1, function(data)
{
var price
...part2
//解析data数据,提取price信息。
show_price("t1",price)
})
}

...
</span>


这个函数必须带有参数site,表明price是来自哪个网站的。

人一切开始优化代码了,就会上瘾,part3可以做成函数,part1,part2为什么不也做成函数咧,

//version 0.3
//////////基础函数/////////
function show_price(site,price)
{
...
//使用price信息。将价格显示在site对应的位置
}

function get_api_url(site,item)
{
...
//根据item生成请求api地址,并返回
}

function get_price(site,data)
{
...
//解析data数据,提取price信息,并返回
}

///////////具体查询函数///////////
function query_t1(item)
{
var api_url_t1= get_api_url("t1",item)
$.get(api_url_t1, function(data)
{
var price = get_price("t1",data)
show_price("t1",price)
})
}

function query_t2(item)
{
var api_url_t2= get_api_url("t2",item)
$.get(api_url_t2, function(data)
{
var price = get_price("t2", data)
show_price("t2",price)
})
}
...

</span>


到这个时候,你就会发现query_t*这种函数非常没有意思,除了t*各不相同,里面的内容完全一样。这不像人类写的代码,好像机器写的代码。

有更好的办法吗?

//version 0.4
///////////具体查询函数///////////
function query(site,item)
{
var api_url = get_api_url(site,item)
$.get(api_url, function(data)
{
var price = get_price(site,data)
show_price(site,price)
})
}
</span>


这里面,$.get的第二参数,是一个匿名函数,它使用query函数的局块变量site.就形成一个闭包。这个匿名函数的具体功能,根据site参数的不同而不同。也可以理解为query函数调用时会根据site参数,生成它相应的匿名函数。

总结一下,闭包给了js函数生成函数的能力,增加了js代码的抽象能力

另外,js中function即可以用于函数定义,又可以用于类的定义,这样我们就可以通过闭包,来实现生成类的类。也就是说类的实例是一个类。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: