揭秘 IIFE 语法

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 本文讲的是揭秘 IIFE 语法,只要你稍微接触过一些 JavaScript,你一定会频繁地接触到下面这个模式 —— IIFE,其全称为 immediately invoked function expression,即“立即调用的函数表达式”:
本文讲的是揭秘 IIFE 语法,

只要你稍微接触过一些 JavaScript,你一定会频繁地接触到下面这个模式 —— IIFE,其全称为 immediately invoked function expression,即“立即调用的函数表达式”:

(function() {
    // ...
})();

一直以来,IIFE 创造的函数作用域被用于防止局部变量泄漏至全局作用域中。类似地,我们可以用 IIFE 来包裹私有状态(或广而言之,数据),这两者本质上是相通的。

想知道 IIFE 的更多用途吗,比如提高代码压缩率?不妨看看@toddmotto 的这篇文章

不过,你可能还是会好奇为什么 IIFE 的语法是这样的?它看上去的确有一点点奇怪,让我们一点一点地来揭开她神秘的面纱吧。

IIFE 语法

IIFE 的核心无非就是一个函数,从 function 关键字开始,到右花括号结束:

function() {
    // ...
}

不过,这可不是一段合法的 JavaScript 代码。当 parser(语法分析器)看到这段语句由 function 关键字开头时,它就会按照函数声明(Function Declaration)的方式开始解析了。可是这段函数声明并没有声明函数名,不符合语法规则。因此解析失败,我们只会得到一个语法错误。

所以我们得想个办法让 JavaScript 引擎把它作为*函数表达式(Function Expression)而非函数声明(Function Declaration)*来解析。如果你还不知道这两者的区别,可以看看原作者这篇有关 JavaScript 中不同声明函数方式差异的文章。

我们使用的技巧其实非常简单。用一个圆括号将函数包裹起来其实就可以消除语法错误了,我们得到以下代码:

(function() {
    // ...
});

一旦遭遇到未闭合的圆括号,parser 就会把两个圆括号之间的语句作为表达式来看待。与函数声明相比,函数表达式可以是匿名的,所以上面这段(被圆括号包着的)函数表达式就成为了一段合法的 JavaScript 代码。

如果你想继续了解 ECMAScript 语法,ParenthesizedExpression 这个部分被详细叙述在规范的 12.2 节.

最后剩下的,就是调用这个函数表达式了。目前为止,这个函数还未被执行。我们也没有将它赋值给任何变量 ,因此我们无法持有它的引用从而之后能用来调用它。我们将要做的是在它后面再加上一对圆括号:

(function() {
    // ...
})();

传说中的 IIFE 就这么出现了。如果你稍微回想一下,就会觉得这个名字再合适不过了:一个被立即调用的函数表达式(immediately invoked function expression)

接下来,我们来看几个在不同原因催生下的 IIFE 变种。

圆括号应该放哪?

我们刚才的做法,是把用于调用函数表达式的圆括号直接放在用于包裹的圆括号之后:

(function() {
    // ...
})();

不过,Douglas Crockford 等人觉得悬荡在外的圆括号太不美观了!所以它们把圆括号移到了里面:

(function() {
    // ...
}());

其实两种做法从功能还是语义上来说都差不多,所以选择一种你喜欢的并坚持下去就好了。

实名 IIFE

被包裹起来的函数其实就是个普通的函数表达式,所以你也可以给它个名字让它变成实名的函数表达式

(function iife() {
    // ...
})();

注意你仍然不能省略用于包裹的括号,下面这段代码仍然是无效的

function iife() {
    // ...
}();

虽然 parser 现在可以成功地把它作为函数声明来解析,但很快,紧跟的 ( 符号就会抛出语法错误了。与函数表达式不同,函数声明并不可以被立刻调用。

避免文件合并时遇到问题

有时,你会看到 IIFE 的前面放了个分号:

;(function() {
    // ...
})();

这个分号被称为防御性分号,用于防止两个 JavaScript 文件合并时可能产生的问题。想象一下假设第一个文件的代码是这样的:

var foo = bar

可以看到这个变量声明语句并没有以分号结尾。如果第二个 JS 文件中的 IIFE 前面没有放分号,合并的结果就会是这样:

var foo = bar
(function() {
    // ...
})();

第一眼看上去好像是一个赋值操作与一个 IIFE。可是事与愿违,我们把 bar 后面的换行去掉就能看清楚了: bar 会被当作一个接受函数类型参数的函数……

var foo = bar(function() {
    // ...
})();

而防御性分号就可以解决这个问题:

var foo = bar;
(function() {
    // ...
})();

就算这个分号前面什么代码也没有,在语法上其实这也是正确的:它会被当做一个空声明(empty statement),无伤大雅。

JavaScript 自动添加分号的特性很容易让意想不到的错误发生。我建议你永远显式地写好分号,以防解释器自己添加。

用箭头函数代替函数表达式

随着 ECMAScript 2015 的到来,JavaScript 的函数声明方式中又多了一个箭头函数(Arrow Function)。箭头函数与函数表达式同属于表达式而非声明语句。所以我们同样可以用它来创造 IIFE:

(() => {
    // ...
})();

不过我并不建议你这么做;我觉得传统的 function 关键字写法的可读性要好得多。





原文发布时间为:2016年04月20日

本文来自云栖社区合作伙伴掘金,了解相关信息可以关注掘金网站。
目录
相关文章
|
3月前
|
JavaScript 前端开发
IIFE(立即执行函数表达式)
IIFE(立即执行函数表达式)
39 1
|
5月前
|
设计模式 自然语言处理 JavaScript
JavaScript进阶-函数表达式与闭包
【6月更文挑战第18天】JavaScript函数不仅是代码块,还是值,具备函数表达式和闭包等特性。函数表达式如匿名函数,可赋值、传参,但不提升,过度使用影响可读性。闭包允许访问外部作用域,即使父函数已结束,但不当使用可能导致内存泄漏。理解并妥善处理这些问题,如命名函数表达式、及时释放引用,能提升代码质量。通过实践深化对这些关键概念的理解至关重要。
35 2
|
6月前
|
JavaScript 前端开发
Symbol在JavaScript中有哪些具体的用法和语法
Symbol在JavaScript中有哪些具体的用法和语法
|
存储 JavaScript 前端开发
JavaScript 函数可以通过一个表达式定义。
JavaScript 函数可以通过一个表达式定义。
56 0
|
JavaScript 前端开发
JavaScript函数篇之ES6箭头函数与匿名函数
对于箭头函数,this 关键字始终表示定义箭头函数的对象。
138 0
|
SQL
函数的语法
函数的语法
107 1
|
JavaScript 前端开发
【JavaScript】21_debug,立即执行函数 与 严格模式
# 14、debug ```html <script> //debugger // 在代码中打了一个断点 console.log(a) // 2 var a = 1 console.log(a) // 1 function a() { alert(2) } console.log(a) // 1 var a = 3
96 0
|
JavaScript
JS 高级(一)RegExp、函数、重载、作用域和作用域链
JS 高级(一)RegExp、函数、重载、作用域和作用域链
147 0
JS 高级(一)RegExp、函数、重载、作用域和作用域链
|
前端开发
less语法(一)变量与extend
less语法(一)变量与extend
|
JavaScript 前端开发
理解下JavaScript中的匿名函数、自执行匿名函数
本文目录 1. 函数也是一种类型 2. 匿名函数 3. 自执行匿名函数 4. 小结
164 0