立即调用函数表达式

在使用JavaScript的时候经常会看见类似如下的函数调用方式:

(function(){ 
    console.log("test");
})();

或者

(function(){ 
    console.log("test");
}());

一些流行的库也是这样,比如jQuery

(function( window, undefined ) {
    // code here
}) ( window );

社区对此种用法的称呼不尽相同,其中包括「自执行匿名函数」(self-executing anonymous function),「立即执行函数表达式」(Immediately-Invoked Function Expression,以下简称IIFE),笔者倾向于第二种叫法。
普通的函数声明与调用方式有以下几种:

// 声明函数f1
function f1() {
    console.log("f1");
}

// 通过()来调用此函数
f1();

// 或者
// 建立匿名函数并赋予变量f2
var f2 = function() {
    console.log("f2");
}

// 通过()来调用此函数
f2();

以上都是以显示的方式声明函数,然后再进行通过()来进行调用,那么如果想要直接调用匿名函数呢?可以做到吗?试一下:

// 直接在匿名函数后边加()(方式A)
function () {console.log("f1");}(); // SyntaxError: Unexpected token (

很不幸,出错了:SyntaxError: Unexpected token (。那么试试前言中的方法:

// 在匿名函数外面套一个(),然后再用()来调用(方式B)
(function(){ console.log("test");})(); // test

// 在方式A的外层套一个()(方式C)
(function(){ console.log("test");}()); // test

以上这两种方式都是可以正常运行的,如果仅仅到这里是不够的,必需要「知其然,知其所以然」,让我们看看为什么A不可以运行,而B和C是可以运行的。原来,JavaScript解释器会在默认的情况下把遇到的function关键字当作是函数声明语句(statement)来进行解释的,而函数声明语句是这样的:

function name([param] [, param] [..., param]) {
   statements
}

A的调用方式明显是有语法错误的,所以才会抛出异常SyntaxError: Unexpected token (。但是B,C为什么可以正常运行?这是因为在JavaScript中()之间只能包含表达式(expression),所以在以B,C方式运行的时候,解释器把()中的内容当作表达式(expression)而不是语句(statement)来执行。为了能够更好的解释B,C调用方式的原理,在这里插入对()操作符的简单介绍:

// 如果传入字面量(literal),则返回表达式(expression)
(1) // 1
(function(){console.log("f");}) // function () {console.log("f")}

这里不会对()做更深入的探讨,如果读者感兴趣,可以自行google。OK,我们先看B的调用方式:

(function(){ console.log("test");})(); // test

由于把函数的声明写在了()之中,所以解释器以表达式(expression)来解析其中代码,而根据我们上面的介绍知道如果向()中传入函数声明会直接返回此函数,此步执行完成之后,临时结果用伪代码来表示的话,应该类似这样:

anonymousFunction();

这个就是函数的通用调用方式,所以继续执行。对于C的调用方式就更加容易理解了,直接把()中的内容当作表达式来进行解释。

所以根据上面的解释,我们知道只要能让JavaScript解释器以「函数表达式」而不是「函数声明」来处理匿名函数的立即执行就可以了,把语句放在()之中只是其中的一种方法而已,根据这个思路我们可以用其他方式来实现同样的目的,比如:

// 如果本身就是expression,那么根本不需要做任何处理

var i = function(){ return 10; }();
true && function(){ /* code */ }();
0, function(){ /* code */ }();

// 如果你不在乎返回值,可以这么做
!function(){ /* code */ }();
~function(){ /* code */ }();
-function(){ /* code */ }();
+function(){ /* code */ }();

// 还有更奇葩的方式,但是不知道性能如何,来自
// http://twitter.com/kuvos/status/18209252090847232
new function(){ /* code */ }
new function(){ /* code */ }()

为什么要用立即执行函数表达式

模拟块作用域

众所周知,JavaScript没有C或Java中的块作用域(block),只有函数作用域,在同时调用多个库的情况下,很容易造成对象或者变量的覆盖,比如:

liba.js

var num = 1;
// code....
libb.js

var num = 2;
// code....

如果在页面中同时引用liba.js和liba.js两个库,必然导致num变量被覆盖,为了解决这个问题,可以通过IIFE来解决:

liba.js

(function(){
    var num = 1;
    // code....
})();
libb.js

(function(){
    var num = 2;
    // code....
})();

经过改造之后,两个库的代码就完全独立,并不会互相影响。

解决闭包冲突

闭包(closure)是JavaScript的一个语言特性,简单来说就是在函数内部所定义的函数可以持有外层函数的执行环境,即使在外层函数已经执行完毕的情况下,在这里就不详细介绍了,感兴趣的可以自行Google。我们这里只举一个由闭包引起的最常见的问题:

var f1 = function() {
    var res = [];
    var fun = null;
    for(var i = 0; i < 10; i++) {
        fun = function() { console.log(i);};//产生闭包
        res.push(fun);
    }

    return res;
}

// 会输出10个10,而不是预期的0 1 2 3 4 5 6 7 8 9
var res = f1();
for(var i = 0; i < res.length; i++) {
    res[i]();
}

修改成:

var f1 = function() {
    var res = [];
    for(var i = 0; i < 10; i++) {
        // 添加一个IIFE
        (function(index) {
            fun = function() {console.log(index);};
            res.push(fun);
        })(i);
    }

    return res;
}

// 输出结果为0 1 2 3 4 5 6 7 8 9
var res = f1();
for(var i = 0; i < res.length; i++) {
    res[i]();
}

模拟单例

在JavaScript的OOP中,我们可以通过IIFE来实现,如下:

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

counter.get(); // 0
counter.set( 3 );
counter.increment(); // 4
counter.increment(); // 5
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容