js闭包总结

页面导航:首页 > 网络编程 > JavaScript > js闭包总结

js闭包总结

来源: 作者: 时间:2016-02-03 09:20 【

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

一、变量的作用域

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

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

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


Js代码

  var n=999;

  function f1(){
    alert(n);
  }

  f1(); // 999

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

Js代码

  function f1(){
    var n=999;
  }

  alert(n); // error

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

Js代码

  function f1(){
    n=999;
  }

  f1();

  alert(n); // 999

--------------------------------------------------------------------------------------------------------

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

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

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

Js代码

  function f1(){

    n=999;

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

  }

在上面的代码中,函数f2就被包括在函数f1内部,这时f1内部的所有局部变量,对f2都是可见的。但是反过来就不行,f2内部的局部变量,对f1 就是不可见的。这就是Javascript语言特有的“链式作用域”结构(chain scope),

子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。

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


Js代码

  function f1(){

    n=999;

    function f2(){
      alert(n);
    }

    return f2;

  }

  var result=f1();

  result(); // 999

--------------------------------------------------------------------------------------------------------

三、闭包的概念

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

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

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

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

--------------------------------------------------------------------------------------------------------b

四、闭包的用途

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

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


Js代码

  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),这时一定要小心,不要随便

改变父函数内部变量的值。

--------------------------------------------------------------------------------------------------------

六、思考题

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

Js代码
  var name = "The Window";
  var object = {
    name : "My Object",
    getNameFunc : function(){
      return function(){
        return this.name;
     };
    }
};
alert(object.getNameFunc()()); //The Window

--------------------------------------------------------------------------------------------------------
JavaScript闭包例子

function outerFun()
{
var a=0;
function innerFun()
{
a++;
alert(a);
}
}
innerFun()

上面的代码是错误的.innerFun()的作用域在outerFun()内部,所在outerFun()外部调用它是错误的.

改成如下,也就是闭包:

Js代码

function outerFun()
{
var a=0;
function innerFun()
{
a++;
alert(a);
}
return innerFun; //注意这里
}
var obj=outerFun();
obj(); //结果为1
obj(); //结果为2
var obj2=outerFun();
obj2(); //结果为1
obj2(); //结果为2

什么是闭包:

当内部函数 在定义它的作用域 的外部 被引用时,就创建了该内部函数的闭包 ,如果内部函数引用了位于外部函数的变量,当外部函数调用完毕后,这些变量在内存不会被 释放,因为闭包需要它们.

--------------------------------------------------------------------------------------------------------

再来看一个例子

Js代码

function outerFun()
{
var a =0;
alert(a);
}
var a=4;
outerFun();
alert(a);

结果是 0,4 . 因为在函数内部使用了var关键字 维护a的作用域在outFun()内部.

再看下面的代码:

Js代码

function outerFun()
{
//没有var
a =0;
alert(a);
}
var a=4;
outerFun();
alert(a);
结果为 0,0 真是奇怪,为什么呢?

作用域链是描述一种路径的术语,沿着该路径可以确定变量的值 .当执行a=0时,因为没有使用var关键字,因此赋值操作会沿着作用域链到var a=4; 并改变其值.

--------------------------------------------------------------------------------------------------------------------------------------------------

如果你对javascript闭包还不是很理解,那么请看下面转载的文章:(转载:http://www.felixwoo.com/archives/247)

一、什么是闭包?

官方”的解释是:闭包是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。
相信很少有人能直接看懂这句话,因为他描述的太学术。其实这句话通俗的来说就是:JavaScript中所有的function都是一个闭包。不过一般来说,嵌套的function所产生的闭包更为强大,也是大部分时候我们所谓的“闭包”。看下面这段代码:

function a() { 
 var i = 0; 
 function b() { alert(++i); } 
 return b;
}
var c = a();
c();

这段代码有两个特点:

1、函数b嵌套在函数a内部;

2、函数a返回函数b。

引用关系如图:

\

  这样在执行完var c=a()后,变量c实际上是指向了函数b,再执行c()后就会弹出一个窗口显示i的值(第一次为1)。这段代码其实就创建了一个闭包,为什么?因为函数a外的变量c引用了函数a内的函数b,就是说:<喎"http://www.2cto.com/kf/ware/vc/" target="_blank" class="keylink">vcD4KPHA+CqGhoaE8c3Ryb25nPrWxuq/K/WG1xMTasr+6r8r9YrG7uq/K/WHN4rXE0ru49rHkwb/S/dPDtcTKsbryo6y+zbS0vajBy9K7uPax1bD8oaM8YnI+Cjwvc3Ryb25nPjwvcD4KPHA+CqGhoaHIw87Sw8fLtbXEuPzNuLO50rvQqaGjy/nOvaGwsdWw/KGxo6y+zcrH1Nq5udTsuq/K/czlxNq2qNLlwe3N4rXEuq/K/df3zqrEv7HqttTP87XEt723qLqvyv2jrLb41eK49rbUz/O1xLe9t6i6r8r9t7S5/cC00v3Tw83isuO6r8r9zOXW0LXEwdnKsbHkwb+ho9Xiyrm1w9a70qrEv7HqILbUz/PU2sn6tObG2sTayrzW1cTcsaOz1sbkt723qKOsvs3E3LzkvdOxo7PW1K25udTsuq/K/czltbHKsdPDtb21xMHZyrGx5MG/JiMyMDU0MDuho76hudzX7r+qyry1xLm51Oy6r8r9tffTw9LRvq294cr4o6zB2cqxseTBv7XEw/uzxtKytrzP+8qnwcujrLWr1NrEvyCx6rbUz/O1xLe9t6jE2si0yrzW1cTc0v3Tw7W9uMOx5MG/tcQmIzIwNTQwO6OstvjH0rjDJiMyMDU0MDvWu8TczajV4tbWt723qMC0t8POyqGjvLTKudTZtM6199PDz+DNrLXEubnU7Lqvyv2jrLWr1ru74cn6s8nQwrbUz/O6zbe9t6ijrNDCtcTB2cqxseTBv9a7yse21NOm0MIgtcQmIzIwNTQwO6Osus3Jz7TOxMe0zrX308O1xMrHuPfX1LbAwaK1xKGjPC9wPgo8cD4KPHN0cm9uZz62/qGisdWw/NPQyrLDtNf308Ojvzxicj4KPC9zdHJvbmc+PC9wPgo8cD4KoaGhobzytvjR1Nauo6yx1bD8tcTX99PDvs3Kx9TaYda00NDN6rKit7W72Lrzo6yx1bD8yrm1w0phdmFzY3JpcHS1xMCsu/i72MrVu/rWxkdDsru74crVu9hhy/nVvNPDtcTXytS0o6zS8s6qYbXExNqyv7qvyv1itcTWtNDQ0OjSqtLAwLVh1tC1xLHkwb+ho9Xiyse21LHVsPzX99PDtcS3x7Oj1rGw17XEw+jK9qOssrvXqNK10rKyu9HPvfejrLWrtPO4xdLiy7y+zcrH1eLR+aOswO294rHVsPzQ6NKq0a3Q8r2lvfi1xLn9s8yhozwvcD4KPHA+CtTayc/D5rXEwP3X09bQo6zTydPasdWw/LXEtObU2sq5tcO6r8r9Ybe1u9i686OsYdbQtcRpyrzW1bTm1NqjrNXi0fnDv7TO1rTQ0GMoKaOsaba8ysfX1LzTMbrzYWxlcnSz9mm1xCYjMjA1NDA7oaM8L3A+CjxwPgqhoaGhxMcgw7TO0sPHwLTP68/zwe3Su9bWx+m/9qOsyOe5+2G3tbvYtcSyu8rHuq/K/WKjrMfpv/a+zc3qyKuyu82swcuho9Lyzqph1rTQ0M3quvOjrGLDu9PQsbu3tbvYuPhhtcTN4r3no6zWu8rHsbthy/nS/dPDo6y2+LTLyrFh0rLWu7vhsbti0v0g08OjrNLytMu6r8r9YbrNYrulz+DS/dPDtavT1rK7sbvN4r3ntPLIxSixu83ivefS/dPDKaOsuq/K/WG6zWK+zbvhsbtHQ7vYytWhoyi52NPaSmF2YXNjcmlwdLXEwKy7+LvYytW7+tbGvavU2rrzw+bP6s+4venJ3Ck8L3A+CjxwPgo8c3Ryb25nPsj9oaKx1bD8xNq1xM6iudvKwL3nPC9zdHJvbmc+PC9wPgo8cD4KoaGhocjnufvSqrj8vNPJ7sjrtcTBy73isdWw/NLUvLC6r8r9YbrNx7bM17qvyv1itcS52M+1o6zO0sPH0OjSqtL9yOvB7c3ivLi49rjFxO6jurqvyv21xNa00NC7t76zKGV4Y3V0aW9uIGNvbnRleHQpoaK77ravttTP8yhjYWxsIG9iamVjdCmhotf308PT8ihzY29wZSmhotf308PT8sG0KHNjb3BlIGNoYWluKaGj0tS6r8r9YbTTtqjS5bW91rTQ0LXEuf2zzM6qwP2y+8r21eK8uLj2uMXE7qGjPC9wPgo8b2w+CjxsaT4KtbE8c3Ryb25nPrao0uU8L3N0cm9uZz66r8r9YbXEyrG68qOsanO94srNxve74b2ruq/K/WG1xDxzdHJvbmc+1/fTw9PywbQoc2NvcGUgY2hhaW4pPC9zdHJvbmc+yejWw86qPHN0cm9uZz62qNLlYcqxYcv51Nq1xKGwu7e+s6GxPC9zdHJvbmc+o6zI57n7YcrH0ru49sirvta6r8r9o6zU8nNjb3BlIGNoYWlu1tDWu9PQd2luZG93ttTP86GjPGxpPgq1sTxzdHJvbmc+1rTQ0Dwvc3Ryb25nPrqvyv1htcTKsbryo6xhu+G9+Mjrz+DTprXEPHN0cm9uZz7WtNDQu7e+syhleGN1dGlvbiBjb250ZXh0KTwvc3Ryb25nPqGjPGxpPgrU2rS0vajWtNDQu7e+s7XEuf2zzNbQo6zK18/Iu+HOqmHM7bzT0ru49nNjb3BlyvTQ1KOsvLRhtcQ8c3Ryb25nPtf308PT8jwvc3Ryb25nPqOsxuQmIzIwNTQwO77Nzqq12jGyvdbQtcRzY29wZSBjaGFpbqGjvLRhLnNjb3BlPWG1xNf308PT8sG0oaM8bGk+Csi7uvPWtNDQu7e+s7vhtLS9qNK7uPY8c3Ryb25nPrvutq+21M/zKGNhbGwgb2JqZWN0KTwvc3Ryb25nPqGju+62r7bUz/PSssrH0ru49tO109DK9NDUtcS21M/zo6y1q8v8sru+39PQ1K3Qzbb4x9Kyu8Tczai5/UphdmFTY3JpcHS0+sLr1rG907fDzsqho7S0vajN6rvutq+21M/zuvOjrLDRu+62r7bUz/PM7bzTtb1htcTX99PD0/LBtLXE1+62pbbLoaO0y8qxYbXE1/fTw9PywbSw/LqswcvBvbj2ttTP86O6YbXEu+62r7bUz/O6zXdpbmRvd7bUz/OhozxsaT4Kz8LSu7K9ysfU2rvutq+21M/zyc/M7bzT0ru49mFyZ3VtZW50c8r00NSjrMv8saO05tfFtffTw7qvyv1hyrHL+bSrtd21xLLOyv2hozxsaT4K1+6687DRy/nT0Lqvyv1htcTQzrLOus3E2rK/tcS6r8r9YrXE0v3Tw9KyzO2807W9YbXEu+62r7bUz/PJz6Gj1NrV4tK7sr3W0KOszeqzycHLuq/K/WK1xLXEtqjS5aOs0vK0y8jnzay12jOyvaOsuq/K/WK1xNf308PT8sG0sbvJ6NbDzqpiy/mxu7ao0uW1xLu3vrOjrLy0YbXE1/fTw9PyoaMKPHA+CrW9tMujrNX7uPa6r8r9YbTTtqjS5bW91rTQ0LXEsr3W6L7NzeqzycHLoaO0y8qxYbe1u9i6r8r9YrXE0v3Tw7j4Y6Os09a6r8r9YrXE1/fTw9PywbSw/Lqswcu21Lqvyv1htcS77ravttTP87XE0v3Tw6Os0rK+zcrHy7Viv8nS1LfDzsq1vWHW0Lao0uW1xMv509Cx5MG/us26r8r9oaO6r8r9YrG7Y9L908OjrLqvyv1i09bSwMC1uq/K/WGjrNLytMu6r8r9YdTat7W72Lrzsru74bG7R0O72MrVoaM8L3A+CjxwPgq1sbqvyv1i1rTQ0LXEyrG68tLgu+HP8dLUyc+yvdbo0rvR+aGj0vK0y6Os1rTQ0MqxYrXE1/fTw9PywbSw/LqswcszuPa21M/zo7pitcS77ravttTP86GiYbXEu+62r7bUz/O6zXdpbmRvd7bUz/OjrMjnz8LNvMv5yr6jujwvcD4KPHA+CjxpbWcgc3JjPQ=="http://www.2cto.com/uploadfile/Collfiles/20150129/20150129085133142.jpg" alt="\">

如图所示,当在函数b中访问一个变量的时候,搜索顺序是:

  1. 先搜索自身的活动对象,如果存在则返回,如果不存在将继续搜索函数a的活动对象,依次查找,直到找到为止。
  2. 如果函数b存在prototype原型对象,则在查找完自身的活动对象后先查找自身的原型对象,再继续查找。这就是Javascript中的变量查找机制。
  3. 如果整个作用域链上都无法找到,则返回undefined。

    小结,本段中提到了两个重要的词语:函数的定义执行。文中提到函数的作用域是在定义函数时候就已经确定,而不是在执行的时候确定(参看步骤1和3)。用一段代码来说明这个问题:

    function f(x) { 
      var g = function () { return x; }
      return g;
    }
    var h = f(1);
    alert(h()); 

    这段代码中变量h指向了f中的那个匿名函数(由g返回)。

    • 假设函数h的作用域是在执行alert(h())确定的,那么此时h的作用域链是:h的活动对象->alert的活动对象->window对象。
    • 假设函数h的作用域是在定义时确定的,就是说h指向的那个匿名函数在定义的时候就已经确定了作用域。那么在执行的时候,h的作用域链为:h的活动对象->f的活动对象->window对象。

      如果第一种假设成立,那输出值就是undefined;如果第二种假设成立,输出值则为1。

      运行结果证明了第2个假设是正确的,说明函数的作用域确实是在定义这个函数的时候就已经确定了。

      四、闭包的应用场景
      保护函数内的变量安全。以最开始的例子为例,函数a中i只有函数b才能访问,而无法通过其他途径访问到,因此保护了i的安全性。

      1. 在内存中维持一个变量。依然如前例,由于闭包,函数a中i的一直存在于内存中,因此每次执行c(),都会给i自加1。
      2. 通过保护变量的安全实现JS私有属性和私有方法(不能被外部访问)
        私有属性和方法在Constructor外是无法被访问的

        function Constructor(...) {
        var that = this;
        var membername = value;
        function membername(...) {...}
        }

        以上3点是闭包最基本的应用场景,很多经典案例都源于此。

        五、script的垃圾回收机制

        在Javascript中,如果一个对象不再被引用,那么这个对象就会被GC回收。如果两个对象互相引用,而不再被第3者所引用,那么这两个互相引用的对象也会被回收。因为函数a被b引用,b又被a外的c引用,这就是为什么函数a执行后不会被回收的原因。

        六、结语

        理解的闭包是迈向高级JS程序员的必经之路,理解了其解释和运行机制才能写出更为安全和优雅的代码。

Tags:

文章评论

最 近 更 新
热 点 排 行
Js与CSS工具
代码转换工具

<