`

javascript 之function的(closure)闭包特性

阅读更多
javascript之 function的闭包(closure)特性

javascript 的 function 具有闭包的特性,是 javascript 语言的一个特色。

一、基础知识:变量的作用域

   要理解闭包,首先必须理解javascript变量的作用域。
   变量的作用域有两种:全局变量和局部变量。

   1. 在javascript中,函数内部可以直接读取全局变量。
var n = 9999;
function f1(){
   alert(n);
}
f1();//9999


   2. 另外一方面,在函数外部无法读取函数内部的局部变量。
function f1(){
   var n = 99;
}
alert(n); //[Error] Uncaught ReferenceError: n is not defined


   3. 这里有一个地方需要注意,函数内部声明变量的时候,一定要使用 var 命令。
      如果不用的话,则向上查找引用,直至全局。 没有找到时,则定义一个全局变量。

      即:如果没有使用 var 关键字创建的变量都是全局变量,即使是在函数内部。

function f1(){
   n =99;
}
f1();
console.log(n);  //99


二、基础知识:如何从外部读取局部变量

   出于种种原因,我们有时候需要得到函数内的局部变量。
   但是,前面已经说过了,正常情况下,这是办不到的,只有通过变通方法才能实现。
   那就是在函数内部再定义一个函数。
fucntion f1(){  
   var n =99;  
   function f2(){  
      alert(n); //99  
   }  
}

   父对象的所有变量,对子对象都是可见的,反之不成立。
   既然f2可以读取f1中的局部变量,那么只要把f2作为返回值,我们不就可以在f1外部读取它的内部变量了吗?
function f1(){
   var n=99;
   function f2(){
      alert(n);
   }
   return f2;
}
var result = f1();

result(); //99


三、闭包的概念及原理

概念:  
   在 javascript 中,function 的闭包是指:
   在 function 运行时产生的局部的上下文(闭包)环境,以 return 的形式被外部引用,而在内存中不被释放。

   可以把具有闭包功能的 function 看成是一个生产闭包的工厂,每执行一次该函数,就会产生一个闭包。
   这个闭包中的变量和函数被外部引用,而被保存在内存中。

  
运行原理:
   根据垃圾回收机制,局部变量在函数运行完毕后就被从内存中移除。
   但是,如果局部变量被外部引用(或外部存在对局部变量的引用),
   则局部变量 及 在其上下文环境内被引用到的变量不会被释放。
   因此,由于局部变量的值还在内存中存在着,当引用闭包时,可以引用到其上下文环境的变量的值。

构成闭包的两个要素:  
   1. 局部变量被外部引用。
      在函数中以 return 形式返回给外部。
   2. 被引用的局部变量类型是函数。
      javascript是解释执行的,无需编译。只有被外部引用的对象是函数时,其上下文才会被保留,而形成闭包。因为此时函数还未执行,只是返回了一个函数的引用。所以需要保留其上下文,以备函数执行时使用。


四、闭包的用途

   闭包的用途有两个,
   一个是可以在函数内部任意定义变量而不必担心命名冲突。
   另一个就是让这些变量存储一些我们需要的值在内存中,不被释放。

   怎么理解呢,看看如下:
function f1(){
    var n =99;

    nAdd = function(){
        n+=1;
    };

    function f2(){
        console.log(n);		   
    }

    return f2;	   
}

//test
var result = f1();

result(); // 99
nAdd();
result(); // 100

   这段代码证明了,函数f1中的局部变量一直保存在内存中,并没有在f1调用后被自动清除。
  
   另一个值得注意的地方,就是"nAdd=function(){n+=1}"这一行,在nAdd前面没有使用var关键字,因此nAdd是一个全局变量,而不是局部变量,所以nAdd可以作为一个setter,在函数外部对函数内部的局部变量进行操作。

   再看下面的例子:
function f2(){  
    var n =99;  
  
    // 定义一个全局变量 
    nAdd = function(){
        n += 1;  
        console.log(n);
    };
}

// 执行函数,生成局部变量上下文
f2();

nAdd();   // 100


//



五、使用闭包的注意点

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

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


六、对比 function 的构造函数

前面已经说过,既然 javascript 的 function 的闭包特性,其本质就是生产一个包含有局部变量的闭包。

问题:  使用 function 的闭包来产生一个包含局部变量的闭包
      与使用 function 的构造函数来new 一个函数的实例有什么不同?
回答:
       1. 变量的访问权限不同。
       2. 在只生产一个闭包的情况下,前者似乎更简洁。


请看下面的例子:

使用闭包:

<!DOCTYPE html>
<html>
<body>

    <p>Counting with a local variable.</p>
    <button type="button" onclick="myFunction()">Count!</button>
    <p id="demo">0</p>

    <script>

        // 定义一个匿名函数,并让它只执行一次。
        // 变量 add 是这个匿名函数产生的闭包。
        var add = (function () {
            var counter = 0;
            return function () {return counter += 1;}
        })();

        // add 函数可以访问到 counter 变量,但:
        // counter 变量并不是 add 函数的一个属性。
        // counter 变量只能通过 add 函数访问到。  
        function myFunction(){
            document.getElementById("demo").innerHTML = add();
        }
        
    </script>

</body>
</html>




使用构造函数同样也可以实现:


<!DOCTYPE html>
<html>
<body>

    <p>Counting with a local variable.</p>
    <button type="button" onclick="myFunction()">Count!</button>
    <p id="demo">0</p>

    <script>

        //定义一个构造函数,可以多次调用。
        function Factory(){
            this.counter = 0;
            this.increase = function(){       
               return ++this.counter;
            }
        }
        // 变量 add 是这个构造函数产生的(instance)实例。
        var add = new Factory();

        
        // add 对象可以访问到 counter 变量
        // counter 变量是 add 对象的一个属性。这个属性的访问权限是对外公开的。
        // 可以使用 ++add.counter 
        function myFunction(){
            document.getElementById("demo").innerHTML = add.increase();
        //  document.getElementById("demo").innerHTML = ++add.counter;
        }


    </script>

</body>
</html>




七、闭包的应用

1、使用闭包:创建 POJO (Plain Old Javascript Object)

有时需要创建一些简单的 Javascript 对象,用来存储一些属性。例如:
var dog = function (name, age) {
    return {
       name: name,
       age: age
    };
}


你可以像上面那样,每需要创建一类对象,就手动写一段代码。但是,完全没必要这么做。使用 Javascript 的闭包特性,可以为你动态的存储和构建:
var POJO = function () {
    // 取得对 function 的 arguments 对象的引用
    // arguments 是一个数组,包括所有调用 function 时输入的参数
    var members = arguments;
    return function () {
        var obj = {}, i = 0, j = members.length;
        for (; i < j; ++i) {
            obj[members[i]] = arguments[i];
        }

        return obj;
    };
};


使用我们创建的 POJO 函数:
//================================
// 创建一个 Dog 构造函数
var Dog = POJO('name', 'age');

// 创建一个 POJO 对象
var dog1 = Dog('Fido', 2);

// 这就是我们创建的 POJO 对象
//   dog1 = {
//       "name" : "Fido",
//       "age"  : 2
//   }

//=================================
// 创建一个 Cat 构造函数
var Cat = POJO('name', 'color', 'age');

// 创建二个 POJO 对象
var cat1 = Cat('Areo', 'black', 2);
var cat2 = Cat('Hellen', 'yellow', 1);





2、使用闭包:任意使用【增加】变量【属性】。并将其私有化,不必担心变量名冲突。

下面的代码实现了一个动态加载的进度条功能。
连续点击加载按钮时,进度条总是从头开始。

先看一下未使用的情况: id 是全局变量
<!DOCTYPE html>q<html>
<head>
    <!-- LINK: 
     http://www.w3schools.com/w3css/default.asp 
    -->
    <title>W3.CSS</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="http://www.w3schools.com/lib/w3.css">

    <style>
    .w3-progress-container-s{
        height:0.15em;
    }
    </style>

</head>
<body class="w3-container">

    <h2>Dynamic Progress Bar</h2>

    <div class="w3-progress-container w3-progress-container-s">
        <div id="myBar" class="w3-progressbar w3-cyan" style="width:1%"></div>
    </div>

    <br>
    <button class="w3-btn w3-dark-grey" onclick="move()">Click Me</button> 

    <script>
    var id;  //  id 是全局变量
    function move() {
          /* 
          if move() method is called repeatedly, then remove the previus interval function.
          */
          if(typeof id !== "undefined"){
            clearInterval(id);
          }
          
          var elem = document.getElementById("myBar");
          var width = 1;
          id = setInterval(frame, 10);
          function frame() {
            if (width >= 75) {
              if( width < 98){
                    width = width + (100 - width) * 0.005;
                    elem.style.width = width + '%'; 
              }else{
                    clearInterval(id);
              }      
            }else{
              width = width + 2;
              elem.style.width = width + '%'; 
            }
          }
    }//end of "move()"
    </script>

</body>
</html> 



只使用函数时所面对的问题:
每次函数执行时,所有函数内部的变量都会被重置。无法记忆上一次函数执行的结果。
如果把这些结果放在全局变量中,有二个问题:(1)可以被任意访问和修改。(2)可能会引起变量名的冲突,覆盖已经存在的变量。

闭包为函数提供了变量存放的地方。这些变量是函数私有的,只能被函数自己引用到。

使用闭包:
id 被私有化,成为局部变量。
私有化的局部变量不会引起和其它变量名冲突,可以任意起名,任意数量(只要内存够用)。

<!DOCTYPE html>
<html>
<head>
    <!-- LINK: 
                http://www.w3schools.com/w3css/default.asp 
    __________________________________________________________________________________________
    -->
    <title>W3.CSS</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="http://www.w3schools.com/lib/w3.css">

    <style>
    .w3-progress-container-s{
        height:0.15em;
    }
    </style>

</head>
<body class="w3-container">

        <h2>Dynamic Progress Bar</h2>

        <div class="w3-progress-container w3-progress-container-s">
          <div id="myBar" class="w3-progressbar w3-cyan" style="width:0"></div>
        </div>
        <br>

        <button class="w3-btn w3-dark-grey" onclick="startLoading()">Click Me</button> 

        <script>
            var startLoading = function(){
                var id;  // id 被私有化,成为局部变量。且不会引起和其它变量名冲突。
                return function move(){
                      /* 
                      if move() method is called repeatedly, then remove the previus interval function.
                      */
                      if(typeof id !== "undefined"){
                        clearInterval(id);
                      }
                      
                      var elem = document.getElementById("myBar");
                      var width = 1;
                      id = setInterval(frame, 10);
                      function frame() {
                        if (width >= 75) {
                          if( width < 98){
                                width = width + (100 - width) * 0.005;
                                elem.style.width = width + '%'; 
                          }else{
                                clearInterval(id);
                          }      
                        }else{
                          width = width + 2;
                          elem.style.width = width + '%'; 
                        }
                      }
                };//end of "move()"                
            }();   
        </script>

</body>
</html> 




3、一个典型的 counter 对象

//
// 变量 i 只能通过 counter 对象提供的方法进行操作。
//
var counter = (function(){
    var i = 0;
    return {
        get: function(){
            return i;
        },
        set: function( val ){
            i = val;
        },
        increment: function() {
            return ++i;
        }
    };
}());


//
//改进为:可设置初始值
var counter = (function(i){
    return {
        get: function(){
            return i;
        },
        set: function( val ){
            i = val;
        },
        increment: function() {
            return ++i;
        }
    };
}(0));




注意:除了函数内部的变量也以被 Closure,函数的参数也可以被 Closure。

例子:
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



例子二:写一个 add 函数,可以这样调用:add(3,5) 或 add(3)(5)
function add(x, y){
    if(y) 
        return x + y;
    else
        return function(y){
            return x + y;
        }
}




—————————————

javascript 函数基础系列文章

1、JavaScript之变量的作用域
2、javascript之变量类型与变量声明及函数变量的运行机制
3、javaScript之function定义
4、javascript之function的prototype对象
5、javascript之function的(closure)闭包特性
6、javascript之function的this   
7、javascript之function的apply(), call()



___________


javascript 面向对象编程系列文章:

    1、javaScript之面向对象编程
    2、javascript之面向对象编程之属性继承
    3、javascript之面向对象编程之原型继承 
   

-



引用请注明
原文出处:  http://lixh1986.iteye.com/blog/1891833

-


-
2016-07-12
-







-
分享到:
评论

相关推荐

    深入理解javascript原型和闭包.pdf

    闭包(Closure)是JavaScript中的另一个重要概念,是指函数和声明该函数的词法环境的组合。闭包允许一个函数访问并操作函数外部的变量。闭包的形成离不开函数嵌套和函数作为返回值的使用。闭包的优点是可以保存状态...

    javascript闭包

    **闭包**是JavaScript中最强大的特性之一,它使得函数能够记住并访问其定义时所在的作用域中的变量。要理解和运用闭包,首先需要理解作用域、作用域链以及垃圾回收机制。 ##### 1.1 闭包的基本定义 闭包是一种...

    JavaScript 中的闭包是指内部函数可以访问外部函数作用域中的变量

    在JavaScript中,闭包(Closure)是一个极其关键的概念,它使得内部函数能够访问到其外部函数的作用域内的变量,即使外部函数已经执行完毕。这一特性是基于JavaScript的函数作用域规则以及函数本身可以作为值进行...

    javascript闭包高级教程

    ### JavaScript闭包高级教程 #### 简介 在JavaScript编程中,“闭包”是...总结来说,闭包是JavaScript中最强大且最复杂的特性之一。正确理解和使用闭包不仅能帮助开发者编写更优雅的代码,还能避免潜在的性能陷阱。

    闭包javascript.pdf

    在计算机科学中,闭包(Closure)是一种函数,它在一个包含一个或多个绑定变量的环境中被评估。当这个函数被调用时,它可以访问那些绑定变量。换句话说,闭包可以记住在其创建时周围存在的任何变量的状态,即使在...

    javascript闭包(Closure)用法实例简析

    本文实例讲述了javascript闭包(Closure)用法。分享给大家供大家参考,具体如下: closure被翻译成“闭包”,感觉这东西被包装的太学术化。下面参考书本和网上资源简单探讨一下(理解不当之处务请留意)。 1、什么是...

    JavaScript中的闭包(Closure)详细介绍

    闭包之所以强大,在于其能够读取函数内部的变量,并且让这些变量始终保持在内存中。闭包可以用于: 1. 创建私有变量,通过函数访问和修改,对外部隐藏实现细节。 2. 实现模块化,能够设计出高内聚低耦合的代码结构...

    细品javascript 寻址,闭包,对象模型和相关问题.docx

    闭包是JavaScript的一个重要特性,它允许一个函数访问并操作其外部作用域中的变量。这种特性使得函数可以记住并访问在其外部作用域定义的变量,即使这些变量已经不在其直接可访问的作用域内。 **1. 闭包的工作原理*...

    学习Javascript闭包(Closure)知识

    Javascript闭包是编程中一种非常重要的概念,尤其在JavaScript中有着独特的作用。闭包的本质是函数内部能够访问并保持对外部函数作用域中变量的引用,即使外部函数执行完毕,这些变量仍然存在于内存中。这一特性使得...

    【JavaScript源代码】js闭包和垃圾回收机制示例详解.docx

    闭包(Closure)是JavaScript中一个非常重要的概念,它涉及到函数及其相关的词法作用域。简单来说,闭包就是能够访问其自身作用域之外变量的函数。这些额外的词法作用域被绑定在函数的生命周期里,使得即使函数已经...

    深入理解javascript函数参数与闭包

    JavaScript中的函数是其一等公民,这意味着它们可以作为变量赋值、作为参数传递以及作为其他函数的返回值。...通过深入学习和实践,你可以更好地理解和利用这些特性,提升你的JavaScript编程水平。

    Javascript闭包(Closure)详解

    要理解闭包,首先必须理解Javascript特殊的变量作用域。 变量的作用域无非就是两种:全局变量和局部变量。 Javascript语言的特殊之处,就在于函数内部可以直接读取全局变量。 var n=999; function f1(){  alert&#...

    js闭包个人理解

    在JavaScript中,闭包(Closure)是一种非常重要的概念,它涉及到函数作用域、变量生命周期以及函数内部对外部作用域的访问等多个方面。本文将基于提供的文件内容,深入探讨JavaScript闭包的基本原理及其应用。 ###...

    JavaScript 匿名函数(anonymous function)与闭包(closure)

    JavaScript中的匿名函数与闭包是两个非常重要的概念,它们在函数式编程以及处理变量作用域方面发挥着关键作用。理解这两个概念对于深入学习JavaScript至关重要。 首先,匿名函数,正如其名,是没有名称的函数,通常...

    javascript闭包真经

    闭包(Closure)是JavaScript中一个非常重要的概念,它涉及到函数和词法作用域两个方面。简单来说,闭包就是一个函数能够访问并操作其外部作用域中的变量,即使该函数在其外部作用域之外被调用也是如此。闭包使得...

Global site tag (gtag.js) - Google Analytics