1

Javascript函数中的闭包

闭包是指有权访问另一个函数作用域中的变量的函数。创建闭包的常见方式就是,在一个函数的内部创建另一个函数。
有关创建作用域链以及作用域链有什么作用的细节对于彻底理解闭包至关重要。在作用域链中,外部函数的活动对象始终处于第二位,外部函数的外部函数的活动对象处于第三位,……直至作为作用域重点的全局环境。

<script type="text/javascript">
    function compare(value1,value2){
        if(value1<value2){
            return -1;
        }else if(value1>value2){
            return 1;
        }else{
            return 0;
        }
    }
    var result=compare(5,10);
    alert(result);
    </script>

图片描述

图1. compare函数作用域链示意图

以上代码先定义了compare函数,然后又在全局作用域中调用了它。当第一次调用时,会创建包含this、arguments、value1、value2的活动对象。全局执行环境的变量对象(包含result、 this、 compare)在compare()执行环境的作用域链中则处于第二位。

图片描述

图2.Compare函数执行时的作用域链

无论什么时候在函数访问一个变量时,就会从作用域链中搜索具有相应名字的变量。一般来讲,当函数执行完毕之后,局部活动对象(这里指compare函数创建的对象,包含this、arguments、value1、value2)就会被销毁,内存中只保留全局作用域(全局执行环境的 变量)。但是,闭包的情况又有所不同:

在另一个函数的内部定义的函数会将包含函数(即外部函数)的活动对象添加到它的作用域链上。
var compareName=createComparationFunction("name");
    var result=compare({name:"Nicho"},{name:"Greg"});
    compareName=null;//解除对匿名函数的引用(以便释放内存)

注:compareName没有函数声明Function,是匿名函数。

在匿名函数从createComparationFunction函数中被返回之后,它的作用域链被初始化为包含createComparationFunction函数的活动对象和全局作用对象。匿名函数可以访问createComparationFunction函数中定义的所有变量。

更为重要的是:函数在执行完毕之后,其活动对象也不会销毁,因为匿名函数的作用域链仍然在引用这个活动对象,直到匿名函数被销毁之后,createComparationFunction函数的活动对象才会被销毁。由于闭包包含它的函数的作用域,因此会比其他函数占用更多的内存。

This:匿名函数具有全局执行环境。

理解闭包预与变量:

function createFunctions(){
        var result=new Array();
        for(var i=0;i<10;i++){
            result[i]=function(){
                return i;
            };
        }
        //alert(result);此时返回[function (){return i;},...,function (){return i;}]
        return result; 
    }
    var arr=createFunctions();

//注意观察下面的输出内容,理解函数的调用时刻和把i的赋值给变量时刻

alert(arr);//[function (){return i;},function (){return i;},...,function (){return i;}]
  alert(arr());//[10,10,……,10]

这个函数会返回长度为10的函数数组。
图片描述

表面上看,似乎每个匿名函数都应该返回自己在数组result中的索引值,但实际上每个函数都返回10。因为每个函数的作用域链中都保存着函数的活动对象,所以他们引用的是同一个变量i,当函数返回后,变量i的值为10。

或许只是知道了这个原理还不能够理解为什么每个函数都返回10,那么你就有可能要了解函数(这里指的是匿名函数)的执行时刻了:在上述函数中的匿名函数在定义函数体之后不会主动执行,所以此时的rusult返回了[function (){return i;},...,function (){return i;}] 数组里面包含10个函数表达式,而不是匿名函数表达式执行之后的返回值组成的数组;而arr()执行时,此时i的值是10,所以每个匿名函数表达式执行后都会返回10。
先来了解一下函数声明、函数表达式和匿名函数

  1. 函数声明:function fnName () {…};使用function关键字声明一个函数,再指定一个函数名,叫函数声明。

  2. 函数表达式:var fnName = function () {…};使用function关键字声明一个函数,但未给函数命名,最后将匿名函数赋予一个变量,叫函数表达式,这是最常见的函数表达式语法形式。

  3. 匿名函数:function () {}; 使用function关键字声明一个函数,但未给函数命名,所以叫匿名函数,匿名函数属于函数表达式!函数有很多作用,赋予一个变量则创建函数,赋予一个事件则成为事件处理程序或创建闭包等等。

函数声明和函数表达式不同之处在于:

Javascript引擎在解析javascript代码时会‘函数声明提升'(Function declaration Hoisting)当前执行环境(作用域)上的函数声明,而函数表达式必须等到Javascirtp引擎执行到它所在行时,才会从上而下一行一行地解析函数表达式
而且表达式后面可以加括号立即调用该函数,函数声明不可以,只能以fnName()形式调用
具体内容请查看有关函数的章节。
接下来我们来学习如何让匿名函数立即执行,来达到预期的目的:
function createFunctions(){

    var result=new Array();
    for(var i=0;i<10;i++){
        result[i]=( function(){
            return i;
            }) ();   
    }
    return result;
}
var arr=createFunctions();
alert(arr);//[0,1,2,……,9]

(function(){return i; }());可以让匿名函数表达式立即执行,此时result[index]里面存的就是匿名函数立即执行后的返回值。
下面贴出立即调用的函数表达式的几种方法(PS:虽然我只试过前三个):

1.最流行的写法:(function(){// do something })();
2.[function(){// do something }()];
3.(function(){// do something }());
4.!function(){ // do something }();
5.~ function() {}();
6.+ function() {}();
7.- function() {}()

8.delete function() {}();
9.typeof function() {}();
10.void function() {}();
11.new function() {}();
12.new function() {};

13.var f = function() {}();
14.1, function() {}();
15.1 ^ function() {}();
16.1 > function() {}();

注:有些人的确会用「自执行的匿名函数」(self-executing anonymous function)这个术语,但是 Ben Alman 推荐了一个更准确的叫法:「立即调用的函数表达式」(IIFE,Immediately-Invoked Function Expression——虽然更长了点,但是这个术语,缩写漂亮,含义更清晰。
function(){// do something }()会提示语法错误:

function(){}()并不是合法的EcmaScript字符串,因为 JavaScript 文法明确规定表达式语句不得以 function 或者 { 开头。而“!function(){}()”是,们从规范出发。

闭包的用途:
闭包可以用在许多地方,它的最大用处有两个:

  1. 一个是可以读取函数内部的变量

  2. 另一个就是让这些变量的值始终保持在内存中。

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

 

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被赋给了一个全局变量result,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。

这段代码中另一个值得注意的地方,就是“nAdd=function(){n+=1}”这一行,首先在nAdd前面没有使用var关键字,因此 nAdd是一个全局变量,而不是局部变量。其次,nAdd的值是一个匿名函数(anonymous function),而这个匿名函数本身也是一个闭包,所以nAdd相当于是一个setter,可以在函数外部对函数内部的局部变量进行操作。
使用闭包的好处:

  1. 希望一个变量长期驻扎在内存当中

  2. 避免全局变量的污染

  3. 私有成员的存在

闭包的用法:

  1. 模块化代码

  2. 在循环中直接找到对应元素的索引

注:函数嵌套函数,内部函数可以引用外部函数的参数和变量,参数和变量不会被垃圾回收机制所收回:

执行say667()后,say667()闭包内部变量会存在,而闭包内部函数的内部变量不会存在.使得Javascript的垃圾回收机制GC不会收回say667()所占用的资源,因为say667()的内部函数的执行需要依赖say667()中的变量。这是对闭包作用的非常直白的描述.
  function say667() {
    // Local variable that ends up within closure
    var num = 666;
    var sayAlert = function() { alert(num); }
    num++;
    return sayAlert;
}
 var sayAlert = say667();
 sayAlert()//执行结果应该弹出的667

问题求解:
function createFunctions(){

    var result=new Array();
    for(var i=0;i<10;i++){
        result[i]= function(){
            return i;
            }();   
    }
    return result;
}
var arr=createFunctions();
alert(arr);//[0,1,2,……,9]

我想立即调用匿名函数来给result[index]赋值,网上查阅有几种方法是:

1.!function(){ // do something }();
2.~ function() {}();
3.+ function() {}();
4.- function() {}()

有人说function(){return i; }();会提示语法错误
实际情况是function(){return i; }();并没有提示错误。而+-!~会改变匿名函数返回值
回答:具体应该说是result= function(){//dosomething}();不会有语法错误吧,function() {//dosomething}();单独执行还是有语法错误的。
result=
1.function(){//dosomething}();
2.!function(){ // do something }();
3.~ function() {}();
4.+ function() {}();
5.- function() {}()

原理是语句function(){//dosomething}()都被当作表达式处理了(赋值前计算表达式的值),就是2、3、4、5例可以自执行,自执行的原因也是function(){//dosomething}()由于前面的符号被当作表达式处理了。
至于result= function(){//dosomething}();不会有语法错误就是因为赋值语句把右边的function(){//dosomething}()当作表达式处理了,然后将结果赋给result。

var someFunction=function(){
//这里是块级及作用域
};
someFunction();
这里定义了一个函数(匿名函数,并把值赋给someFunction)然后立即调用了它(在函数名称后面添加一对括号)。

function(){
//这里是块及作用域
}();
这段代码会导致语法错误,是因为javascript将function关键字当作一个函数声明的开始,而函数声明后面不能跟圆括号。而表达式后面可以跟圆括号,要将函数声明变成表达式,只要向下面那样给它加一对圆括号即可:

(function(){
//这里是块及作用域
})();

specialcoder
2.2k 声望170 粉丝

前端 设计 摄影 文学