详解javascript当即执行函数表达式(IIFE)

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

1.问题java

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

    //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 )

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

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

 

2.概念code

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

(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的属性。

好了,就这么多。

相关文章
相关标签/搜索