JS之立即执行函数讲解

小云云
小云云 原创
2018-03-27 17:09:10 992浏览

本文主要和大家分享JS之立即执行函数讲解,所谓立即执行函数(Immediately-Invoked Function Expression),即在函数表达式之后加()就可以使函数自执行。注意:不能再函数声明后加括号()。

(function foo(){/* code */}()); //正确,推荐这样使用
(function foo(){/* code */})(); //正确,这样可以
var foo = function foo(){/* code */}(); //正确
function foo(){/* code */}(); //SyntaxError: Unexpected token (
// 但是如果你在括弧()里传入一个表达式,将不会有异常抛出
// 但是foo函数依然不会执行
function foo(){ /* code */ }( 1 );
 
// 因为它完全等价于下面这个代码,一个function声明后面,又声明了一个毫无关系的表达式: 
function foo(){ /* code */ }
( 1 );
// 由于括弧()和JS的&&,异或,逗号等操作符是在函数表达式和函数声明上消除歧义的
// 所以一旦解析器知道其中一个已经是表达式了,其它的也都默认为表达式了
// 不过,请注意下一章节的内容解释
var i = function () { return 10; } ();
true && function () { /* code */ } ();
0, function () { /* code */ } ();

// 如果你不在意返回值,或者不怕难以阅读
// 你甚至可以在function前面加一元操作符号
!function () { /* code */ } ();
~function () { /* code */ } ();
-function () { /* code */ } ();
+function () { /* code */ } ();

// 还有一个情况,使用new关键字,也可以用,但我不确定它的效率
// http://twitter.com/kuvos/status/18209252090847232
new function () { /* code */ }
new function () { /* code */ } () // 如果需要传递参数,只需要加上括弧()

作用域

IIFE可以为局部变量提供一个封装的作用域,在IIFE返回的函数中能够访问该变量。这种方式即允许函数访问这个本地变量,即使这个函数在IIFE的词法范围之外执行时。

const uniqueId1 = function() {
    let count1 = 0;
    return function() {
        ++count1;
        return count1;
    };
};
uniqueId1(); //ƒ () {++count1;return count1;}
const uniqueId2 = function() {
    let count = 0;
        return count;
};
uniqueId2(); //0
uniqueId2(); //0
uniqueId2(); //0
const uniqueId3 = (function() {
    let count3 = 0;
    return function() {
        ++count3;
        return count3;
    };
})();
uniqueId3(); //1
uniqueId3(); //2
uniqueId3(); //3
uniqueId3(); //4
const uniqueId4 = function() {
    let count4 = 0;
    return (function() {
        ++count4;
        return count4;
    })();
};
uniqueId4(); //1
uniqueId4(); //1
uniqueId4(); //1
uniqueId4(); //1

注意:在IIFE之外无法访问这个计数变量count。除了从IIEF中返回的函数,别人无法读写该变量。这样就能创建真正的私有状态,它只能以受控的方式进行修改。

Module模式非常依赖IIFE这种机制。当使用IIFE来返回一个”封闭”一些本地变量来管理私有数据的函数时,letconst都不能替代它。

// 创建一个立即调用的匿名函数表达式
// 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只存在于闭包)

闭包与IIFE

和普通function执行的时候传参数一样,自执行的函数表达式也可以这么传参,因为闭包直接可以引用传入的这些参数,利用这些被lock住的传入参数,自执行函数表达式可以有效地保存状态。

// 这个代码是错误的,因为变量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);

}

// 你也可以像下面这样应用,在处理函数那里使用自执行函数表达式
// 而不是在addEventListener外部
// 但是相对来说,上面的代码更具可读性

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');

}

问题1、

for (var i = 0; i < 5; i++) {
  setTimeout(function(i) {
    console.log(i);
  }, i * 1000);
}

问题2、

for (var i = 0; i < 5; i++) {
  setTimeout((function(i) {
    console.log(i);
  })(i), i * 1000);
}

问题3、

for (var i = 0; i < 5; i++) {
  setTimeout((function(i) {
    return function() {
        console.log(i);
    }
  })(i), i * 1000);
}

问题4、

for (var i = 0; i < 5; i++) {
    setTimeout((function(i) {
        console.log(i);
    }).bind(this,i), i * 1000);
}

建议写法:

var j = 0;
for (i = 0; i < 5; i++) {
  setTimeout(function() {
     console.log(j);
     j++;
  }, i * 1000);
}

之所以用到立即调用/执行的函数表达式,主要作用是模仿块级作用域(也叫私有作用域):避免向全局作用域中添加过多的变量和函数。这样每个开发人员既可以使用自己的变量,又不必担心会影响或搞乱全局作用域。

IIFE打包压缩

1、参数变短

IIFE可以进行参数变量名混叠,如下:

window.$ = function somethingElse() {
    // ...
};
 
(function($) {
    // ...
})(jQuery);

为了解决命名冲突问题,可以将一段代码封装在一个IIEF中,将一个全局变量(比如,jQuery)作为参数传入IIFE。在函数内部,就可以以一个任意的参数名(比如,$)来访问该参数值。

IIFE的这种特性可以用来优化代码,这种方式使代码能够被更有效的压缩。例如:

(function(window, document, undefined) {
    // ...
})(window, document);

可以缩短函数的参数名为单个字母的标识符,更短标识符名会使文件的体积变得更小。

(function(w, d, u) {
    // ...
})(window, document);

2、括号和分号的使用

我们知道,以下两种方法都是立即执行函数的写法:

// 下面2个括弧()都会立即执行
(function () { /* code */ } ());
(function () { /* code */ })();

注意这两种写法:匿名函数上面的写法都存在前后文;问题,所以需要注意的是匿名函数在压缩工具打包压缩后会出现上下文错误合并()的问题,例如第二种写法。如果下面的代码,未压缩之前是正常的,压缩后就不正常了,所以要严格上下文的;问题,而第一种就不会出现类似问题。

var a="bbb"
(function(){
	alert(1);
})();
//var a = function(){}
var a="bbb"
(function(){
	alert(1);
}());

上述代码会报""bbb" is not a function"错误,若变量a是一函数,则会报"undefined is not a function",这是因为a变量或a函数会把他后面的匿名函数作为参数传入a中,这就很好的解释了为什么报前面提到的错误,这也很好地解释了为什么有人习惯在匿名函数之前添加;了,就是为了防止上文没有严格遵循javascript语法,漏掉;的问题。

相关推荐:

js立即执行函数的方法

js立即执行函数实例详解

实例详解JavaScript中立即执行函数

以上就是JS之立即执行函数讲解的详细内容,更多请关注php中文网其它相关文章!

声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn核实处理。
上一条:js之圣杯模式讲解 下一条:js分页器详解