zoukankan      html  css  js  c++  java
  • JavaScript之闭包问题以及立即执行函数

    今天我将会来浅谈一下关于JavaScript的立即执行函数以及闭包问题。 
    首先我们先要了解一下关于立即执行函数:

    ( function(){…} )()( function (){…} () )是两种javascript立即执行函数的常见写法,最初我以为是一个括号包裹匿名函数,再在后面加个括号调用函数,最后达到函数定义后立即执行的目的,后来发现加括号的原因并非如此。要理解立即执行函数,需要先理解一些函数的基本概念。

    函数声明、函数表达式、匿名函数

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


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


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

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

    一、Javascript引擎在解析javascript代码时会‘函数声明升’(Function declaration Hoisting)当前执行环境(作用域)上的函数声明,而函数表达式必须等到Javascirtp引擎执行到它所在行时,才会从上而下一行一行地解析函数表达式;

    二、函数表达式后面可以加括号立即调用该函数,函数声明不可以,只能以fnName()形式调用 。以下是两者差别的两个例子。

    fnName();
    function fnName(){
        ...
    }
    //正常,因为‘提升'了函数声明,函数调用可在函数声明之前
    
    fnName();
    var fnName=function(){
        ...
    }
    //报错,变量fnName还未保存对函数的引用,函数调用必须在函数表达式之后

    在理解了一些函数基本概念后,回头看看( function(){…} )()和( function (){…} () )这两种立即执行函数的写法,最初我以为是一个括号包裹匿名函数,并后面加个括号立即调用函数,当时不知道为什么要加括号,后来明白,要在函数体后面加括号就能立即调用,则这个函数必须是函数表达式,不能是函数声明。

    (function(a){
        console.log(a);   //firebug输出123,使用()运算符
    })(123);

    闭包: 
    闭包,简单来说就是函数嵌套函数,或者说定义在一个函数内部的函数,它是将函数内部和函数外部连接起来的一座桥梁。

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

    例1:请看下面代码:

    //闭包概念:函数嵌套函数
    function t2() {
        var b=100;
        function t3() {
            console.log(b);
        }
        return t3();
    }
    t2();
    代码

    运行结果为:

    这里写图片描述

    在这段代码中,在函数t2内部声明的变量b本来是一个局部变量,为什么在调用时t3函数能打印出b变量的值呢?原因如下:

    在上面的代码中,函数t3就被包括在函数t2内部,这时t2内部的所有局部变量,对t3都是可见的。但是反过来就不行,t3内部的局部变量,对t2就是不可见的。这就是Javascript语言特有的”链式作用域”结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。

    这就是闭包的其中一个作用,可以读取函数内部的一个变量。


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

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

    为了深入理解以上所讲内容,请看以下代码段:

    // 这个代码是错误的,因为变量i从来就没被locked住
    // 相反,当循环执行以后,我们在点击的时候i 才获得数值
    // 因为这个时候i操真正获得值
    // 所以说无论点击那个连接,最终显示的都是I am link #10(如果有10个a元素的话)
    
    var elems = document.getElementsByTagName('a');
    
    for (var i = 0; i < elems.length; i++) {
    
        elems[i].addEventListener('click', function (e) {
            e.preventDefault();
            alert('I am link #' + i);
        }, 'false');
    
    }
    
    // 这个是可以用的,因为他在自执行函数表达式闭包内部
    // i的值作为locked的索引存在,在循环执行结束以后,尽管最后i的值变成了a元素总数(例如10)
    // 但闭包内部的lockedInIndex值是没有改变,因为他已经执行完毕了
    // 所以当点击连接的时候,结果是正确的
    
    var elems = document.getElementsByTagName('a');
    
    for (var i = 0; i < elems.length; i++) {
    
        (function (lockedInIndex) {
    
            elems[i].addEventListener('click', function (e) {
                e.preventDefault();
                alert('I am link #' + lockedInIndex);
            }, 'false');
    
        })(i);
    
    }

    var elem = document.getElementsByTagName('div'); // 如果页面上有5个div
    
    for(var i = 0; i < elem.length; i++) {
        elem[i].onclick = function () {
            alert(i); // 总是5
        };
    }

    上方是一个很常见闭包问题,点击任何div弹出的值总是5,因为当你触发点击事件的时候i的值早已是5,可以用下面方式解决:

    var elem = document.getElementsByTagName('div'); // 如果页面上有5个div
    
    for(var i = 0; i < elem.length; i++) {
        (function (w) {
            elem[w].onclick = function () {
                alert(w); // 依次为0,1,2,3,4
            };
        })(i);
    }

    转自csdn:http://blog.csdn.net/sinat_35512245/article/details/53514804

    部分代码参考阮一峰的网络日志

  • 相关阅读:
    java spring-mvc + maven + hibernate + mysql 注释
    c# log4net
    c# winform richtextbox 锁屏和滚屏
    socket 客户端
    c# winform插件
    c# 注册全局热键
    c# 请求api获得json数据
    java 把一个文件夹里图片复制到另一个文件夹里
    c# UpdateLayeredWindow异形窗口
    【哈希】身份证问题
  • 原文地址:https://www.cnblogs.com/dongcong/p/6694222.html
Copyright © 2011-2022 走看看