使用立即执行函数(IIFE)编写JavaScript模块

在 JavaScript 开发过程中,通常将实现特定功能的一组方法定义为一个模块,实现一个模块在代码结构和语法上有多种思路。

原始简陋写法

这种模式就是将不同的函数以及所需的变量简单地放置在一起,形成一个模块。例如:

function add(){  
    //... some code
}

function remove(){  
    //... some code
}

上面的代码中,函数add()remove()组成了一个模块。使用的时候直接调用对应函数。

这种模式的明显缺点就是所有函数以及变量均直接挂载在window全局变量下,污染了全局变量,使用时没有模块的概念,不能区分哪个方法是哪个模块的,模块的成员之间看不出直接的关系,另外也很有可能不同模块之间的方法名或变量名冲突。

对象写法

这种写法就是为了解决上面写法的缺点,把模块写成一个对象,所有模块成员均为这个对象中成员,此写法可以看做设计模式中的单例模式,示例代码如下:

var module = {  
    _param: '', //..._前缀表示局部变量,习惯写法
    add: function(){
        //...some code
    },
    remove: function(){
        //...some code
    }
};

上面的函数add()remove()均封装在module对象内部。使用时即调用这个对象的属性。

module.add();  

这种写法还有一个缺点就是暴露了本应该局限于模块内部的局部变量_param,模块外部可直接调用或改写,例如:

module._param = 'this is called outside the module';  

一个变量或方法应该在其设计的功能范围内可见和有效,而不应该暴露于其设计的功能范围之外,这样才符合简洁、严格的 JavaScript 设计和代码规范。因此,推荐本文的标题所提及的立即执行函数写法。

立即执行函数写法

什么是立即执行函数?

立即执行函数英文Immediately Invoked Function Expression,简称 IIFE。

实际上我们编写 JavaScript 代码过程中已经使用过它,就是常说的自执行函数,可能我们写过自执行匿名函数比较多。具体可以参照一篇文章immediately-invoked-function-expression

现在使用该写法来实现一个简单的模块,代码如下:

var module = (function(){  
    var _param = '';
    var add = function(){
        //...some code
    };
    var remove = function(){
        //...some code
    };
    return {
        add: add,
        remove: remove
    };
})();

上面的代码声明了一个自执行的匿名函数,将内部的add()remove()方法属性暴露出来,但不暴露私有成员变量_param,保证了内部变量的安全性。

放大模式与宽放大模式

如果一个模块很大,可以将模块分为若干部分,或者一个模块继承于另一个模块,就需要采用放大模式(augmentation)。

var module = (function(mod){  
    mod.modify = function(){
        //...some code
    };
    return mod;
})(module);

上面的代码通过 IIFE 为module模块扩展了新方法modify()并返回新的模块module

但同时我们需要考虑另一个问题,浏览器的加载顺序有时无法预知,如果被继承的模块的代码还未执行,即上述方法可能扩展一个不存在的空对象,这时就有必要采用宽放大模式(Loose augmentation),代码如下:

var module = (function(mod){  
    //...some code
    return mod;
})(window.module||{});

与放大模式相比,其主要区别为提供给立即执行函数的参数可以是一个空对象。

最后

IIFE 实际上是 JavaScript 实现模块的最重要方式,扩展阅读可参照 ben cherry 的著名文章: JavaScript Module Pattern: In-Depth