javascript 闭包(Closure)

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

一、变量作用域

要理解闭包,首先要理解javascript的特殊的变量作用域。变量的作用域分两种:全局变量和局部变量

javascript语言的特别之处就在于:函数内部可以直接读取全局变量,但是在函数外部无法读取函数内部的局部变量

注意点:在函数内部声明变量的时候,一定要使用var命令。如果不用的话,你实际上声明的是一个全局变量!

  var n=999;
  function f1(){
    alert(n);
  }
  f1(); // 999  函数内部可以直接读取全局变量

        // ---------------分割线---------------------

  function f1(){
    var n=999;
  }
  alert(n); // error  在函数外部无法读取函数内部的局部变量

        // ---------------分割线---------------------
  function f1(){
    n=999;
  }
  f1();  // 函数内部定义的方法和变量,要等到函数执行过以后,才会真正定义;一定要先执行函数,否则报错“n is not defined" 
  alert(n); // 999  函数内部声明变量的时候,一定要使用var命令。如果不用的话,你实际上声明了一个全局变量!

二、如何从外部读取函数内部的局部变量?

我们有时候需要获取到函数内部的局部变量。但是,上面已经说过了,正常情况下,这是办不到的!只有通过变通的方法才能实现。

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

  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是 function f2(){alert(n); }  是一个闭包;
  result(); // 999

三、闭包的概念

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

各种专业文献的闭包定义都非常抽象,我的理解是: 闭包就是能够读取其他函数内部变量的函数。

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

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

四、闭包的用途

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

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

  function f1(){
    var n=999;
    nAdd=function(){n+=1} //nAdd 没有Var声明,说以是一个全局变量,它的值是一个匿名函数,也是一个闭包;
    function f2(){  //f2是一个闭包,可以读取函数f1内部的变量的函数
      alert(n);
    }
    return f2;
  }
  var result=f1(); //f1()的执行结果是function f2(){ alert(n);};f2被赋值给了全局变量result,而f2是f1的子函数,f2依赖f1而存在,所以f1(以及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),这时一定要小心,不要随便改变父函数内部变量的值。

六、其他

闭包是运行时中的概念,不能讲哪个函数是一个闭包!而是哪个函数在运行时存在一个闭包!有时候,好几个函数都可以组成一个闭包呢:

function ff(){
    var local=1;
    this.add1=function(){
        return ++local;
    };
    this.add2=function(){
        return ++local;
    }
}
var f=new ff(); //返回一个new表达式创建的对象,此对象拥有在ff()中添加的方法add1和add2;
alert(f.add1());  //2  
alert(f.add2());  //3  闭包的作用:上一次调用后变量local还保存在内存中并没有被释放,所以在上一次的local基础上+1

七、需要理解的代码

var add = (function () {    //变量 add 指定了函数自执行后的返回字值
    var counter = 0;  
    return function () {return counter += 1;}
})();    //自执行函数只执行一次。将自执行结果复制给add变量;后面执行add时就直接执行返回结果的代码;设置计数器为 0。并返回函数表达式。

add();  //add变量可以作为一个函数使用。它可以访问函数上一层作用域的计数器。
add();   // add就是一个闭包。它使得函数拥有私有变量变成可能; 
add();   //计数器counter 受匿名函数的作用域保护,只能通过 add 方法修改

// 计数器为 3
//闭包会持有父方法的局部变量并且不会随父方法销毁而销毁, 所以这个counter其实就是来自于第一次function执行时创建的变量。
function makeAdder(x) {
  return function(y) {
    return x + y;
  };
}

var add5 = makeAdder(5);
var add10 = makeAdder(10);

console.log(add5(2));  // 7
console.log(add10(2)); // 12
var counter = (function() {
  var privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }
  return {
    increment: function() {
      changeBy(1);
    },
    decrement: function() {
      changeBy(-1);
    },
    value: function() {
      return privateCounter;
    }
  };   
})();

console.log(counter.value()); // logs 0
counter.increment();
counter.increment();
console.log(counter.value()); // logs 2
counter.decrement();
console.log(counter.value()); // logs 1
var makeCounter = function() {
  var privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }
  return {
    increment: function() {
      changeBy(1);
    },
    decrement: function() {
      changeBy(-1);
    },
    value: function() {
      return privateCounter;
    }
  }  
};

var counter1 = makeCounter();
var counter2 = makeCounter();
alert(counter1.value()); /* Alerts 0 */
counter1.increment();
counter1.increment();
alert(counter1.value()); /* Alerts 2 */
counter1.decrement();
alert(counter1.value()); /* Alerts 1 */
alert(counter2.value()); /* Alerts 0 */

参考:学习Javascript闭包(Closure)- 阮一峰

发布了26 篇原创文章 · 获赞 3 · 访问量 7944

猜你喜欢

转载自blog.csdn.net/Eva3288/article/details/79196674