匿名函数 & 闭包 ( 7 章 )

   1:  function functionName(arg0,arg1){
   2:   
   3:   
   4:   
   5:  }
   6:   
   7:  var functionName = function( arg0 , arg1 ){
   8:   
   9:   
  10:   
  11:  }

匿名函数时一种强大的令人难以置信的工具

这两种的区别,  前者会在代码执行以前被加载到作用域中, 而后者则是在代码执行到那一行的时候才会有定义.

另外的区别是, 函数声明( 前者) 会给函数一个名字, 而函数表达式(后者) 则是创建一个匿名函数, 然后把这个匿名函数赋给一个变量 functionName.

关于函数声明, 它的一个重要特征就是 函数声明提升, 意思是在执行代码之前会先读取函数声明, 这就意味着可以把函数声明放在调用它的语句后面.

//匿名函数, 直接写也是可以的,只不过没有什么实际意义。

把函数想成值时, 都可以使用匿名函数 例如 :

在将函数作为参数传入另一个函数,或者从一个函数中返回另一个函数时,通常都要使用以这种形式来定义匿名函数。

   1:  function createCompare(propertyName){
   2:   
   3:      return function( object1, object2 ){
   4:   
   5:          var value1 = object1[ propertyName] ;
   6:   
   7:          var value1 = object2[ propertyName] ;
   8:   
   9:          if ( value1 < value2 ){
  10:   
  11:              return 1 ;
  12:   
  13:           }else {
  14:   
  15:               return -1;
  16:   
  17:            }
  18:   
  19:         
  20:   
  21:      } ;
  22:   
  23:  }
递归 ( 用途 1 )
   1:  <SPAN style="COLOR: #000000">function factorial( num ){
   2:   
   3:    if(num <=1 ){
   4:   
   5:    return 1 ;
   6:   
   7:    } else {
   8:   
   9:      return num * factorial( num -1 ) ;
  10:   
  11:    }
  12:   
  13:  }
  14:   
  15:  var anotherFactorial = factorial ;
  16:   
  17:  factorial = null ;
  18:   
  19:  alert( anotherFactorial(4) ) ;      // 出错, 因为此时 factorial = null , 已经不再可用, 所以第一次调用没问题, 因为使用 anotherFactorial 指针, 可以找到对应的函数, 但是函数内部,
  20:   
  21:                                                         // 由于是递归函数, 所以函数内部的名字 factorial 失效 ( null ) , 所以就会出错
  22:   
  23:        return arguments.callee( num - 1 ) ;   // 可以解决问题, 之前有提到过, 因此递归函数时, 使用 arguments.callee 总比使用函数名好
  24:  </SPAN>

使用 arguments.callee( num –1 );

   1:  function factorial( num ){
   2:   
   3:    if (num <= 1){
   4:   
   5:        return 1;
   6:   
   7:     } else (
   8:   
   9:        return num * arguments.callee( num -1 )          // 其中 arguments.callee 是一个指向正在执行的函数的指针
  10:   
  11:     )
  12:   
  13:  }
  14:   
  15:  var anotherFactorial = factorial ;
  16:   
  17:  factorial = null ;
  18:   
  19:  alert( anotherFactorial(4) ) ;
闭包

闭包是指 有权访问另一个函数作用域中的变量的函数. 创建闭包的常见方式, 是在一个函数内部创建另一个函数,建议非必要时, 不要使用闭包,匿名函数也是差不多。

即,闭包是一个函数,是能够访问别的函数作用域变量的函数,那么内部函数肯定能访问外部函数的变量, 所以,此时就用到了匿名函数 ( 闭包 ), 所以闭包和匿名函数是什么关系的,是包含关系,即闭包肯定是个匿名函数。例如:

   1:  function createComparisonFunction(propertyName){
   2:   
   3:    return function( object1, object2){
   4:   
   5:    var value1 = object1[propertyName];
   6:   
   7:    var value2 = object2[propertyName];
   8:   
   9:    if (value1 < value2){
  10:   
  11:      return -1;
  12:   
  13:    } else if ( value1 > value2){
  14:   
  15:      return 1;
  16:   
  17:    } else {
  18:   
  19:      return 0;
  20:   
  21:    }
  22:   
  23:   };
  24:   
  25:  }

内部函数访问了外部函数变量 prototyName,即使这个内部函数被返回了,而且是在其他地方被调用了,它仍然可以访问变量 prototyName,内部函数的作用域链包括外部函数,

当某个函数第一次被调用时, 会创建一个执行环境及相应的作用域链, 并把作用域链赋值给一个特殊的内部属性[Scope] , 然后, this, arguments 和其他命名参数的值来初始化函数的活动对象.但在作用域链中, 内部函数-> 外部函数-> 更外部函数->......-> 最外部函数. ( 这种包含关系 )

   1:  function compare(value1, value2){
   2:      if(value1 < value2){
   3:          return -1;
   4:      }else if(value1 > value2){
   5:          return 1;
   6:      }else{
   7:          return 0;
   8:      }
   9:          
  10:  }
  11:   
  12:  var result = compare(5,10);

image

对于这个例子中compare()函数的执行环境,其作用域链中包含两个变量对象,本地活动对象和全局变量对象,显然,作用域链本质上是一个指向变量对象的指针列表,它只引用但不实际包含变量对象。

后台的每个执行环境都有一个表示变量的对象-变量对象, 全局环境的变量对象始终存在, 而像compare()函数这样的局部环境变量对象, 则只在函数执行的过程中存在, 在创建compare()函数时, 会创建一个预先包含全局变量对象的作用域链, 这个作用域链保存在内部的[[Scope]]属性中, 当调用 compare()函数时, 会为函数创建一个执行环境, 然后通过复制函数的[[Scope]]属性中的对象构建起执行环境的作用于链. 此后, 又有一个活动对象(在此作为变量对象使用) 被创建并被推入执行环境作用域链的前端, 对于这个例子 compare() 函数的执行环境而言, 其作用域链中包含两个变量对象, 本地活动对象和全局变量对象, 显然作用域链本质上是一个指向变量对象的指针列表, 它只引用但不实际包含变量对象.

无论什么时候在函数中访问一个变量时,就会从作用域链中搜索具有相应名字的变量,一般来讲,当函数执行完毕后,局部活动对象就会被销毁,内存仅保存全局作用域,但是闭包的情况不同.

在另一个函数内部定义的函数会将包含函数( 即 外部函数 )的活动对象添加到它的作用域链中。

 

承接以上代码例子 :

var compare = createComparisonFunction(“name”);  //compare指针保存函数地址

var result = compare( {name : “Nicholas”}, {name : “Greg”});

在匿名函数从 createComparisonFunction()中被返回后,它的作用域链被初始化为包含createComparisonFunction()函数的活动对象和全局对象,这样,匿名函数就可以访问createComparisonFunction()中定义的所有变量, 更为重要的是, createComparisonFunction()函数执行完毕后,其活动对象也不会被销毁,因为匿名函数的作用域链仍然在引用这个活动对象。(那么匿名函数为什么没有被释放呢? 因为createComparisonFunction 这个函数本身返回的是一个函数, 所以compare=createComparisonFunction 这条语句的本质就是又做了一个指针指向了内存中的匿名函数, 所以在释放空间时, 因为匿名函数在外围函数的外围还有一个指针指向, 所以它不能被释放, 同样它指向的外围函数内部的变量也不能被释放)换句话说,当createComparisonFunction()函数返回后,其执行环境的作用域链会被销毁,但它的活动对象仍然会留在内存中,直到匿名函数被销毁后,createComparisonFunction()的活动对象才会被销毁。所以需要显示解除 :  例如:

// 创建函数

var compareNames = createComparisonFunction("name"); // 这是 compareNames 已经是指向了函数内部的匿名函数

// 调用函数, // 注意, 这个函数执行完毕后, 函数的内存空间将被释放, 但是... 匿名函数和匿名函数所指向的函数的内部变量不能被释放

var result = comareNames({name: "Nicholas"}, {name: “Greg”});

// 解除对匿名函数的引用( 以便释放内存 )

compareNames = null;

image

注意: ( anonymous ) Scope 中的 this 是 window, 这也就呼应了下边的 this 的问题.

关于函数参数传递(特别是匿名函数 )

argument
1 /* 匿名函数传参办法 */
2 
3 /* 01. 普通函数 */
4 
5 var aa = createFunction( xx );    // xx为实参
6 
7 /* 02. 匿名函数 */
8 var aa = function(num){}(argu);    // num为形参, argu为实参

 

作用域链的副作用,即闭包只能取得包含函数中的任何变量的最后一个值,而不是某个特殊变量。

   1:  function createFunctions(){
   2:      var result = new Array();
   3:      for(var i=0; i<10; i++){
   4:          result[i] = function(){
   5:              return i;
   6:          };
   7:      }
   8:      return result;
   9:  }
  10:   
  11:  var funcs = createFunctions();
  12:  //每个函数都输出10
  13:  for(var i=0; i<funcs.length; i++){
  14:      document.write(funcs[i]());
  15:  }

注意:

var funcs = createFunctions();  // 这个 createFunctions() 函数会执行, 并将返回结果 赋值给 funcs, 那么返回的是数组, funcs 就是数组, 返回的是函数 funcs 是函数.

var funcs = createFunctions;    // 这个 createFunctions 函数不会执行, 只是又定义了一个指针指向该函数.

首先, 来说createFunction函数的返回结果, 它所返回的不是一个数字, 而是一个函数, 所以, 当我们接到返回结果时, 因为是一个函数, 而funcs[i]() 表示执行这个函数, 而此时执行这个函数, 这个函数只有1条语句 return i, 而这时因为已经退出了 createFunction函数, 所以i的值是9. 所以最后显示的结果全是9, 之前有误区 :

误区1 createFunctions()函数返回的数值, 首先 return result 是返回语句, 表示此函数返回的是一个数组, 那么数组中装的是什么的, 很直接的是函数, 而并非是 i 的值, 如果是i的值, 那么后面输出时也就不用写成funcs[i]()了, 而直接写成 funcs[i]就可以了. 注意闭包内部名没有执行, 因为开始是个赋值语句, result[i]=function(){}; 这个赋值成功了就可以了, 函数内部并没有执行, 函数必须后面跟()后才能执行, 剩下的就是单纯的赋值, 不要想的那么复杂.

误区2 以为result[i]=function(){}; 这一步会执行function里的内容, 其实直到funcs[i]() 这个语句的时候, 闭包里的内容才真正的执行, 包括 var funcs = createFunctions(); 这条语句, 闭包都没有执行, 这条语句时 createFunction 这个函数执行了, 并把结果 包含有函数的数组返回给了funcs, 这个时候 funcs就是一个数组了, 并且数组元素是一个function, 所以才可以调用 funcs[i](), 来执行数组元素函数.

上例中,每个函数都返回10,而不是1,2,3,4,5,6,7,8,9,10,因为每个函数的作用域链中都保存着createFunctions()函数的活动对象,所以它们引用的都是 同一个变量 i , 当createFunctions()函数返回后,变量 i 的值是 10 , 此时每个函数都引用这保存变量 i 的同一个变量对象,(如上, var funcs = createFunctions() 这条语句会执行 createFunctions函数, 执行后, 函数内的 i 的值就是10了, 并且将数组返回给了 funcs, 此时 funcs 就是一个数组, 而数组元素是 函数, 注意, 此处的数组元素并非数字, 所以当下边又执行 document.write(funcs[i]()))时, 实际上是在执行数组元素里的函数, 也就是匿名函数, 而此时, 匿名函数会去调用createFunctions 中的 i , 而此时, i 已经是10了, createFunction这个函数还在作用域链的原因就是因为有匿名函数还在引用它. 所以整个 createFunction函数的作用域都没有被释放. )

 

所以在每个函数内部 i 的值都是 10。改进如下 :

   1:  function createFunctions(){
   2:      var result = new Array();
   3:      for(var i=0; i<10; i++){
   4:          result[i] = function(num){
   5:              return function(){
   6:                  return num;
   7:              }(i);
   8:                          
   9:          };
  10:      }
  11:      return result;
  12:  }
  13:   
  14:  var funcs = createFunctions();
  15:   
  16:  for(var i=0; i<funcs.length; i++){
  17:      document.write(funcs[i]());
  18:  }

这里, 我们没有直接把闭包赋值给数组, 而是定义了一个匿名函数, 并立即执行该匿名函数, 将结果赋值给数组, 这里匿名函数的参数是 num, 每次执行时会将 i 传递进去, 由于函数参数是按值传递的, 所以就会将变量 i 的当前值赋值给参数 num, 而在这个匿名函数内部, 又创建了一个访问 num 的闭包, 这样一来, result 数组中每个函数都有自己的 num 变量的一个副本, 因此就有各自不同的值了. 注意这里的匿名函数, 如下:

result[i] = function(num) {  // 匿名函数

  return function() {    // 闭包, 所有的闭包都是匿名函数

    return num;

  }

}(i);    // (i) 表示要执行

这个匿名函数之所以能够立刻执行, 就是因为后边的(i), 函数名称后边带(), 就表示要执行函数, 正式因为要执行函数, 所以 var funcs = createFunctions(); 要执行函数 createFunctions, 而进入createFunction 后, 又要执行函数 function(num), 注意上边例子的闭包是不需要执行这个的, 这里就有一个闭包, result[i] 里边存放的也都是函数, result[i] 里的函数不需要执行, 但是此时的 num 所处的环境是在 function(num) 这里, 这里每个 num 的值已经不同, 当再执行 document.write(funcs[i]()); 调用闭包的匿名函数时, 同样此时在执行 result[i] 里的函数, 而这时的函数所返回的内容是 num, 而 num 因为每个所处的环境不同, 都在各自的 function(num) 环境中, 所以它们之间没有影响, 所以最后返回的结果是, 1,2,3,4,5,6,7,8,9,10.

 

function createFunction() {
    var result = new Array();
    for (var i=0; i<=9; i++) {
        result[i] = function() {
            return i;    
        }();
    }
    return result;
}

var funs = createFunction();
for(var i=0; i<funs.length; i++) {
    alert(funs[i]);
}

所以, 以上是更简单的得到0,1,2,3,4,5,6,7,8,9的方法, 只要在原来的基础上, result[i] = function(){return i;}(); 加上这一对小括号, 让这个闭包在函数内部执行就可以了

 

 

this对象

在闭包中使用 this 对象也可能会有点问题,this对象是在运行时基于函数的执行环境绑定的,在全局函数中,this等于window,在函数被作为某个对象的方法调用时,this等于那个对象。不过, 匿名函数的执行环境具有全局性, 因此 this 对象通常指向 windows.

   1:  var name = "The window";
   2:   
   3:  var object = {
   4:      name : "My Object",
   5:      getNameFunc : function(){
   6:          return function(){
   7:              return this.name;
   8:          }
   9:      }
  10:  };
  11:   
  12:  alert(object.getNameFunc());    //"The window"

以上代码先创建了全局变量 name, 又创建了一个包含 name 属性的对象, 这个对象还包含一个方法-getNameFunc(), 它返回一个匿名函数, 而匿名函数又返回 this.name,

为什么上边例子执行结果是 The window ?

前面曾经提过, 每个函数在被调用时, 其活动对象都会自动取得两个特殊变量 this 和 arguments, 内部函数在搜索这两个变量时, 只会搜索到活动对象为止, 因此永远不可能访问到外部函数中的这两个变量, 不过, 把外部作用域中的 this对象保存在一个闭包能够访问的到的变量里, 就可以让闭包访问到该变量了,

个人总结, 因为闭包会将它所在的函数的作用域链全部保存起来, 所有就有类似的事情, 闭包->外部函数->再外部函数->全局, 这是闭包的作用域链, 因为闭包全部保存了, 而又由于闭包只能取得以上作用域链的最后值, 例如上边例子 i, 都是显示10 一样, 所以当你定义的变量名字 name 与环境变量同名时, 闭包要搜索到作用域链最后的这个 name, 所以就后这个例子就返回了 全局 name 属性.

 

匿名函数的执行环境具有全局性,所以它的 this 属性都是 window, 并且 this 是关键字。看下边例子 :

this
 1 var name = "The window";
 2 
 3 var object = {
 4     name : "My object",
 5     getNameFunc : function(){
 6         var that = this;    // 此时是可以访问this的, 因为this是
 7                         // 这个对象的一个属性, 
 8         return function() {  // 闭包 
 9             return that.name;    // that 作为外层函数的活动对象,
10                                           // 会被引用(注意不是this, this不会
11                              // 被引用, 
12         };
13     }
14 };

之所以这个会显示我们想要的结果, 是因为在这个作用域链上没有 that 同名的属性, 所以当闭包搜索 that 属性时, 这个 Object 里的 that 就是最顶端了, 换句话说是最后了, 所以自然就要显示这个 that 的结果了.

模仿块级作用域

JavaScript中没有块级作用域的概念, 例如 for 循环中的 i , 当离开 for 循环时, 也还继续存在, 而在 C 和 Java 这些语言中就不一样了,

function outputNumbers(count) {

  for (var i=0; i<count; i++) {

    alert(i);

  }

  alert(i);  // 此处的 i, 仍然可以调用, 因为没有块级别作用域

}

 

一般来说, 我们应该尽量少向全局作用域中添加变量和函数. 通过这种创建私有作用域, 每个开发人员既可以使用自己的变量, 又不必担心搞乱全局作用域.

   1:  (                               //此处的 (
   2:   
   3:    function(){
   4:   
   5:      //这里是块级作用域
   6:   
   7:    }
   8:   
   9:  ) ( ) ;                        // 此处的 ) ( ) ;

以上代码定义并立即调用了一个匿名函数, 将函数声明包含在一对圆括号中, 表示它实际上是一个函数表达式, 而紧随其后的另一对圆括号会立即调用这个函数.

如果只是单纯的写 function(){

                        }();

这样会出现错误,因为 Javascript 将 function 关键字当做一个函数声明的开始,而函数声明后面不能跟圆括号,然而,函数表达式后面可以跟圆括号(表示执行函数),要将函数声明转换成函数表达式,只要在function前后加一个圆括号就可以了, 即

( function {

}) ();

无论什么地方,只要临时需要一些变量,就可以使用似有作用域,例如 :

似有作用域
1 function outputNumbers(count) {
2     (function() {
3         for (var i=0; i<count; i++) {
4             alert(i);
5         }
6     })();
7     alert( i );    //导致一个错误
8 }

 

   1:  var someFunction = function () {
   2:   
   3:      // 这里是块级别作用域
   4:   
   5:  } ; 
   6:   
   7:  someFunction();      //调用函数

调用函数的方式是在函数名后边加一对圆括号 (), 即 someFunction()

临时使用一些变量时, 可以使用这种块级作用域

   1:  (
   2:   
   3:    function(){
   4:   
   5:      for ( var i = 0 ; i < count ; i++ ) {
   6:   
   7:               alert( i ) ;
   8:   
   9:      }
  10:   
  11:    }
  12:   
  13:  ) () ;
  14:   
  15:    alert ( i ) ;     // 导致一个错误
私有变量

JavaScript中没有私有成员概念, 不过, 有一个私有变量的概念, 任何函数中定义的变量, 都可以认为是私有变量, 外部不能访问函数内部的私有变量, 但是, 可以通过在函数内部创建一个闭包( 即内部函数) , 那么闭包通过自己的作用域链也可以访问这些变量, 而利用这一点, 就可以创建用于访问私有变量的公有方法.

我们把有权访问私有变量和私有方法的公有方法称为 特权方法.

第一种,构造函数中定义的特权方法:

   1:  function MyObject(){
   2:   
   3:          var privateVariable = 10 ;         //私有变量
   4:   
   5:          function privateFunction() {
   6:   
   7:             return false ;
   8:   
   9:          }
  10:   
  11:          this.publicMethod = function() {       // 特权方法
  12:   
  13:                     privateVariable++;
  14:   
  15:                     return PrivateFunction();
  16:   
  17:          } ;
  18:   
  19:  }

另一种方法:

   1:  ( function(){
   2:      //似有变量和私有函数
   3:      var privateVariable = 10;
   4:      function privateFunction(){
   5:          return false;
   6:      }
   7:   
   8:      //构造函数
   9:      MyObject = function(){};
  10:   
  11:      //公有特权方法
  12:      MyObject.prototype.publicMethod = function(){
  13:          privateVariable++;
  14:          return privateFunction();
  15:      };
  16:    }
  17:   
  18:  )();

Good
 1 ( function() {
 2     var name = "";    //私有变量
 3    Person = function(value) {
 4         name = value;
 5     }; 
 6    Person.prototype.getName = function() {
 7         return name;
 8     };
 9     Person.prototype.setName = function(value) {
10         name = value;
11     };
12 
13      
14 } ) ();
15 
16 var person = new Person("Nicholas");
17 alert( person.getName() );    // "Nicholas"
18 person.setName("Greg");
19 alert( person.getName() );    // "Greg"
posted @ 2012-09-21 20:06  神之一招  阅读(2624)  评论(3编辑  收藏  举报