JavaScript IIFE 详解:原理、写法与模块化实践

作者:木棉

时间:2026-10-21

浏览:259

本文详细解析JavaScript立即执行函数表达式(IIFE)的核心原理,包括括号的作用、参数传递机制及防止undefined被篡改的技巧。重点对比普通写法、放大模式与宽放大模式,探讨多文件加载时的命名空间管理与代码合并注意事项,帮助开发者掌握前端模块化的基础实现方式。

文章目录

IIFE的基本概念与定义

一、IIFE解释

IIFE的全称是Immediately Invoked Function Expression,中文译为立即执行的函数表达式。

如下代码展示了一个典型的匿名立即执行函数结构:


(function(window, undefined){
 // 代码... 
})(window);

IIFE中括号的作用机制

二、括号的意义

2.1 包住function(){}的括号的意义

外层括号的核心作用是将function(){}从语句转换为表达式。许多库的源码中常采用类似方式,例如:


~function(){
 // 代码...
}();

或者采用以下形式:


+function(){
 // 代码...
}();

这两种方式的作用一致,都是将函数定义转化为可执行的表达式,从而允许其被立即调用。

若移除外层括号,代码将直接报错。因为单纯的function(){}属于函数声明语句,而非表达式,无法直接执行。具体报错情况如下图所示:

IIFE的基本概念与定义

2.1 第二个括号的意义

在理解第一个括号的作用后,第二个括号的意义便十分清晰:它用于执行前面生成的表达式。

IIFE的参数传递与undefined处理

三、参数的意义

以下代码为例,解析参数传递机制:


var wall = {};
(function(window, WALL, undefined){
})(window, wall);

参数分为形参和实参两部分。

在function(window, WALL, undefined)中,三个参数为形参;而在第二个括号(window, wall)中,两个参数为实参。

这可以理解为形参与实参的对应关系:window对应window,WALL对应wall。

2.1 普通形参

普通形参由window和wall等实际变量传入,可以是任意类型的变量。每个形参严格对应一个实参。

2.2 特殊形参undefined

为何形参中要额外声明一个undefined?这是一个值得探讨的问题。

在上述示例中,实参仅有两个,而形参有三个。因此,在函数执行时,形参undefined会默认被赋值为undefined。

形参undefined的主要作用如下:

2.2.1 防止特殊值undefined被恶意代码篡改。

在IE6等低版本浏览器中,undefined是可被修改的。一旦该全局值被篡改,如下判断逻辑将失效:


if(wall == undefined){
 // 代码...
}

因此,增加一个形参undefined的目的,正是为了规避此类风险。只要在该IIFE的作用域内,undefined就能保持其原始值。

2.2.2 压缩代码时可以压缩undefined。

由于undefined作为形参存在,像YUI compressor这类代码压缩工具可以对其相关值进行压缩,从而减小文件体积。

IIFE的常见写法与模式对比

四、写法解析

4.1 普通写法


var wall = {}; // 声明定义一个命名空间wall
// 定义方法
(function(window, WALL, undefined){
 // 给wall命名空间绑定方法say
 WALL.say = function(){
  console.log('hello');
 };
})(window, wall);
(function(window, WALL, undefined){
 // 给wall命名空间绑定方法 whoIam
 WALL.whoIam = function(){
  console.log('wall');
 };
})(window, wall);
// 调用
wall.say();
wall.whoIam();

先定义一个命名空间,再向该命名空间添加内容。这是最常见且易于理解的写法。

其不足之处在于必须先声明命名空间,随后才能执行绑定代码,存在加载顺序依赖的问题。

4.2 放大模式


var wall = (function(window, WALL, undefined){
 if(typeof WALL == 'undefined'){
  WALL = {};
 }
 // 给wall命名空间绑定方法say
 WALL.say = function(){
  console.log('hello');
 }
 return WALL; // 返回引用
})(window, wall);
var wall = (function(window, WALL, undefined){
 if(typeof WALL == 'undefined'){
  WALL = {};
 }
 // 给wall命名空间绑定方法 whoIam
 WALL.whoIam = function(){
  console.log('wall');
 }
 return WALL; // 返回引用
})(window, wall);
// 调用
wall.say();
wall.whoIam();

放大模式的优势在于无需考虑代码加载的先后顺序。

由于JavaScript允许对wall变量进行重复var声明,因此这段代码可以正常执行。

我们可以将IIFE函数拆分为多个文件进行加载,从而避免普通写法中需要注意的顺序问题。

需要注意以下几点:

1. IIFE的头部需先检查命名空间是否已实例化,若未实例化则进行初始化。

2. IIFE的尾部需return命名空间的引用,以便后续代码获取最新的wall命名空间内容。

4.3 宽放大模式


(function(window, WALL, undefined){
 // 给wall命名空间绑定方法say
 WALL.say = function(){
  console.log('hello');
 }
})(window, window.wall || (window.wall = {}));
(function(window, WALL, undefined){
 // 给wall命名空间绑定方法 whoIam
 WALL.whoIam = function(){
  console.log('wall');
 }
})(window, window.wall || (window.wall = {}));
// 调用
wall.say();
wall.whoIam();

宽放大模式的关键在于实参部分的window.wall || (window.wall = {})。

这里利用||运算符进行逻辑取巧。

如果window.wall已经实例化(非not defined),则直接返回window.wall的引用并赋值给形参WALL,不会执行||运算符后面的内容。

如果window.wall尚未实例化,则进行实例化。需注意,实例化是一个赋值操作,必须用括号包裹以转化为表达式执行,否则报错。

表达式(window.wall = {})执行完毕后,会返回新对象window.wall的引用。

宽放大模式的好处:支持将代码切割为多个文件加载,无需考虑文件加载的先后顺序,不存在强耦合关系。

当然,如果IIFE内部的方法存在互相引用,仍可能存在加载依赖问题。此问题可通过Require.js等加载器解决,此处不再展开讨论。

多文件加载时的注意事项

五、分文件加载IIFE要注意的点


;(function(window, WALL, undefined){
 // 给wall命名空间绑定方法say
 WALL.say = function(){
  console.log('hello');
 }
})(window, window.wall || (window.wall = {}));

细心的读者可能已发现区别:文件开头处先写上一个分号;。

这样,在多个文件合并时,可避免首尾相接导致的代码错乱。例如以下情况:


// a.js 文件
wall.log()
// b.js 文件
(function(window, WALL, undefined){
 // 给wall命名空间绑定方法say
 WALL.say = function(){
  console.log('hello');
 }
})(window, window.wall || (window.wall = {}));

由于a.js文件中wall.log()少写了分号,与b.js文件合并后,JavaScript引擎会认为‘wall.log()(...)'需要被这样执行,从而导致代码报错。

觉得不错的,可以关注模块化这个系列的文章,容我后续码字,敬请期待!

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持本站!

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章