zoukankan      html  css  js  c++  java
  • 详解javascript立即执行函数表达式(IIFE)

    立即执行函数,就是在定义函数的时候直接执行,这里不是申明函数而是一个函数表达式

    1.问题

    在javascript中,每一个函数在被调用的时候都会创建一个执行上下文,在函数内部定义的变量和函数只能在该函数内部调用,正是因为这个上下文,使得在调用函数的时候可以创建一些私有变量。如下代码

        //makeCounter,返回一个新的函数(闭包),这个函数可以访问makeCounter里的局部变量i
        function makeCounter() {
          var i = 0;
          return function () {
            document.write(++i);
            document.write('<br>');
          }
        }
    
        //counter1和counter2是不同的实例,分别拥有自己范围内的变量i
        var counter1 = makeCounter();
        counter1();
        counter1();
    
        var counter2 = makeCounter();
        counter2();
        counter2();

    这里i是函数makeCounter函数内的局部变量,所以定义的counter1和counter2都有自己的变量i,上面代码输出结果如下:

    注意闭i始终保存在内存中,所以第二次调用的时候输出的是2。

    普通情况下我们定义一个函数,然后在语句中函数名字后面加上一对圆括号就可以直接调用它,能不能定义完之后直接在后面加上小括号调用呢?如下

    function(){ counter1(); }(); // SyntaxError: Unexpected token (

    答案是不行,这样会报错的。为什么呢?在javascript解释代码的时候,遇到function关键字的时候就认为这里是一个函数声明而不是函数表达式,如果没有显式地定义成函数表达式就会报错,因为函数声明需要一个函数名,上面的代码没有函数名。

    既然是因为没有函数名字报错那好就加上一个函数名,如下:

    function foo(){ counter1(); }(); // SyntaxError: Unexpected token )

    依然会报错,为什么呢?在一个函数声明语句(这次是正确的)后面加上一对圆括号,这对圆括号和前面的声明语句没有任关系,而只是一个分组操作符,用来控制运算的优先级,这里的意思是小括号里面优先计算,所以上面代码等同于:

    function foo(){ counter1(); }
    (); // SyntaxError: Unexpected token )

    2.概念

    正确的写法是怎样的呢?简单,如下:

    (function () { counter1(); }());

    这样为什么就可以呢?在javascript里圆括号内不能包含语句,当解释器对代码进行解释的时候遇到圆括号就认为这里面是表达式,然后遇到function关键字就认为这是一个函数表达式,而不是函数声明。而更加奇妙的是只要是能将后面语句预先解释为表达式都可以,不一定是分分组操作符,于是立即执行函数表达式有了五花八门的写法,如下:

        (function () { counter1(); }());
        (function () { counter1(); })();
        var i = function(){ counter1(); }();
        true && function () { counter1(); }();
        0, function(){ counter1() }();
        !function () { counter1(); }();
        ~function () { counter1(); }();
        -function () { counter1(); }();
        +function () { counter1(); }();

    输出结果如下:

    甚至可以这样:

        new function(){ counter1(); }
        new function(){ counter1(); }() // 带参数

    这样:

    var i = function(){ counter1(); }();
    var j = (function(){ return 10; }());

    这是为什么呢?因为new,=是运算符,和+,-,*,/一个样,都会把后面的语句预先解释为表达式。这里推荐上面一种写法,因为function内部代码如果太多,我们不得不滚到最后去看function(){}后是否带有()。

    3.立即执行函数和闭包有什么关系

     和普通函数传参一样,立即执行函数也可以传递参数。如果在函数内部定一个函数,而里面的那个函数能引用外部的变量和参数(闭包),我们就能用立即执行函数锁定变量保存状态。

    我们在hmtl页面中方两个超链接标签,然后用下面的代码来测试:

    <div>
        <ul>
            <li><a>第一个超链接</a></li>
            <li><a>第二个超链接</a></li>
        </ul>
    </div>
        var elems = document.getElementsByTagName('a');
        for(var i=0; i<elems.length; i++) {
          elems[i].addEventListener('click', function (e) {
            e.preventDefault();
            alert('I am click Link #' + i);
          }, 'false')
        }

    这段代码意图是点击第一个超链接提示“I am click Link #0”,点击第二个提示“I am click Link #1”。真的是这样吗? 不是,每一次都是“I am click Link #2”

    因为i的值没有被锁住,当我们点击链接的时候其实for循环早已经执行完了,于是在点击的时候i的值已经是elems.length了。

    修改代码如下:

        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 cliick Link #' + i);
            }, 'false')
          })(i)
        } 

    这次可以正确的输出结果,i的值被传给了LockedIndex,并且被锁定在内存中,尽管for循环之后i的值已经改变,但是立即执行函数内部的LockedIndex的值并不会改变。

    还可以这样写:

        var elems = document.getElementsByTagName('a');
        for ( var i = 0; i < elems.length; i++ ) {
          elems[ i ].addEventListener( 'click', (function( lockedInIndex ){
            return function(e){
              e.preventDefault();
              alert( 'I am link #' + lockedInIndex );
            };
          })( i ), 'false' );
        }

    但是我觉得如果用let是不是就可以一下子解决了:

       var elems = document.getElementsByTagName('a');
        for(let i=0; i<elems.length; i++) {
          elems[i].addEventListener('click', function (e) {
            e.preventDefault();
            alert('I am click Link #' + i);
          }, 'false')
        }

    let是块级作用域内的变量,是es6新定义的,这里不展开。

    4.模块模式

    立即执行函数在模块化的时候也有用,用立即执行函数处理模块可以减少全局变量造成的空间污染,而是使用私有变量。

    如下创建一个立即执行的匿名函数,该函数返回一个对象,包含要暴露给外部的属性i,如果不实用立即执行函数就要多定义一个属性i了,这个i就会显示的暴露给外部,这样:counter.i,这种方式明显不太安全。

        var counter = (function(){
          var i = 0;
    
          return {
            get: function(){
              return i;
            },
            set: function( val ){
              i = val;
            },
            increment: function() {
              return ++i;
            }
          };
        }());
        document.write('<br>');
        document.write(counter.get());document.write('<br>');
        document.write(counter.set( 3 ));document.write('<br>');
        document.write(counter.increment());document.write('<br>'); // 4
        document.write(counter.increment());document.write('<br>'); // 5

    注意,这里如果使用counter.i来访问这个内部变量,会报错undefined,因为i并不是counter的属性。

    好了,就这么多。

  • 相关阅读:
    spoj1812 Longest Common Substring II( 后缀自动机 )
    SPOJ8222 Substrings( 后缀自动机 + dp )
    BZOJ 2882: 工艺( 后缀自动机 )
    NOIP2015酱油记
    NOIP2015前
    BZOJ 1088: [SCOI2005]扫雷Mine( )
    BZOJ 1261: [SCOI2006]zh_tree( 区间dp )
    HDU4027 Can you answer these queries? 线段树
    ZOJ3261 Connections in Galaxy War 并查集
    POJ 1773 Parity game 带权并查集
  • 原文地址:https://www.cnblogs.com/tylerdonet/p/6593829.html
Copyright © 2011-2022 走看看