全面理解JavaScript立即执行函数

想要理解立即执行函数,先了解函数声明与函数表达式的区别:

函数声明

1
2
3
function ([param,[, param,[..., param]]]) {
[statements]
}

函数表达式

1
2
3
let function_expression = function [name]([param1[, param2[, ..., paramN]]]) {
statements
};

name可以省略,这种情况下的函数时匿名函数。

两者的不同之处

  1. 函数声明可在当前作用域下提前调用执行,函数表达式需等执行到该函数后,方可执行,不可提前调用。
  2. 函数表达式可直接在函数后加括号调用。而函数声明不能再后面加括号直接调用。

问题的核心

问题的核心就是函数声明和函数表达式通过()调用的问题。

请看下面的代码:

1
var foo = function(){}();

这行代码执行起来是没有问题的,因为函数表达式可以在函数后面加括号直接调用。

1
2
3
function () {} ();

//Chrome报错:Uncaught SyntaxError: Unexpected token (

报错是因为在javascript代码解释时,当遇到function关键字时,会默认把它当做是一个函数声明,而不是函数表达式,如果没有把它显视地表达成函数表达式,就报错了,因为函数声明需要一个函数名,而上面的代码中函数没有函数名,以上代码在执行到第一个左括号的时候报错。所以,Firefox和Chrome本质上报的是同一个错误。

1
2
function foo() {} ();
//SyntaxError: expected expression, got ')'

在一个表达式后面加括号表示该表达式立即执行,但是在一个函数声明后面加括号,括号并不会对前面的函数声明产生影响,以上代码等同于:

1
2
function foo() {};
();

()内的表达式不能为空,所以报错。

立即执行函数(IIFE)

上面我们发现了问题的核心,就是()前面必须是函数表达式。

下面看立即执行函数的两种写法:

1
2
3
(function(){ 
...
})();
1
2
3
(function(){ 
...
}());··

这两种写法都是为了将匿名的函数声明转换为函数表达式。在JavaScript中,()是一种运算符,()内部不能包含声明,只能包含表达式,当解析器对代码进行解释的时候,会自动将()里面的代码识别为表达式,而不是函数声明。

所以,从理论上来讲,除了()运算符,其他任何运算符都可以达到类似的效果,而之所以使用括号,是因为括号相对其他运算符会更安全。

IIFE的使用

  1. 与闭包配合使用来锁住状态

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    大专栏  全面理解JavaScript立即执行函数e">18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    // 并不会像你想象那样的执行,因为i的值没有被锁住
    // 当我们点击链接的时候,其实for循环已经执行完了
    // 于是在点击的时候i的值其实已经是elems.length了
    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的值传给了lockedIndex,并且被锁在内存中
    // 尽管for循环结束后i的值已经改变,但是立即执行函数内部lockedIndex的值并不会改变
    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 );

    }
  2. module模式

    IIFE内部形成一个单独是作用域,封装一下外部无法读取的私有变量,通过return来返回外部可以访问的数据。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    // 创建一个立即调用的匿名函数表达式
    // return一个变量,其中这个变量里包含你要暴露的东西
    // 返回的这个变量将赋值给counter,而不是外面声明的function自身

    var counter = (function () {
    var i = 0;

    return {
    get: function () {
    return i;
    },
    set: function (val) {
    i = val;
    },
    increment: function () {
    return ++i;
    }
    };
    } ());

    // counter是一个带有多个属性的对象,上面的代码对于属性的体现其实是方法

    counter.get(); // 0
    counter.set(3);
    counter.increment(); // 4
    counter.increment(); // 5

    counter.i; // undefined 因为i不是返回对象的属性
    i; // 引用错误: i 没有定义(因为i只存在于闭包)

猜你喜欢

转载自www.cnblogs.com/liuzhongrong/p/12365282.html
今日推荐