IIFE 学习总结
由于在实践过程中看到了两种 JavaScript 的写法,由此使我想更深一步的了解 IIFE 的定义及用法。在此看到了一篇好文 Immediately-Invoked Function Expression (IIFE) 及它的译文 JavaScript:立即执行函数表达式(IIFE)
1 | (function() { |
1 | (function() { |
当我看到这两种写法的时候,脑中疑问为什么要写成这种写法,尤其的在一个 JavaScript file 的开头,把所有 code 全部写在了这种结构中。
什么是 IIFE
在 JavaScript 里,当每个函数被调用时,都会创建一个新的执行上下文。
所以在函数里创建的变量和函数是只能被函数内部访问的,函数外部是不能访问函数内部声明的变量和函数的。
在函数执行过程中,函数内部声明的变量会保存在内存中,当函数执行结束后,这些变量会被删除,内存会被释放。
1 | function makeCounter() { |
IIFE 核心
众所周知,无论以怎样的方式定义函数 function foo(){} 或者 let foo = function(){},在执行的时候都要在后面加一对圆括号,foo()。
1 | // 下面两种定义的函数可以通过在函数名后加一对括号进行调用 |
如果这么使用,这里会抛出一个错误。
当圆括号为了调用函数出现在函数后面时,无论在全局环境或者局部环境里遇到这样的 function 关键字,默认的,他会将它当做是一个函数声明,而不是函数表达式。如果你不明确地告诉圆括号它是一个表达式,它会将其当作没有名字的函数声明并且抛出一个错误,因为函数声明需要一个名字。
1 | // 即使这样调用,同样会抛出错误,下面会说明原因 |
函数,圆括号,错误
但是,如果你为一个函数指定一个名字并且在它后面放一对圆括号,同样抛出错误,但这次是因为另外一个原因。当圆括号放在一个函数表达式后面指明了这是一个被调用的函数,而圆括号放在一个声明后面便意味着完全的和前面的函数名称分开了,此时圆括号只是一个简单的代表一个括号(用来控制运算优先的括号)。
1 | // 然而函数声明语法上是无效的,它仍然是一个声明,紧跟着的圆括号是无效的,因为圆括号里需要包含表达式 |
立即执行函数表达式(IIFE)
幸运的是,修正语法错误很简单。
最流行的也最被接受的方法是将函数声明包裹在圆括号里来告诉语法分析器去表达一个函数表达式,因为在JavaScript里,圆括号不能包含声明。
因为这点,当圆括号为了包裹函数碰上了 function关键词,它便知道将它作为一个函数表达式去解析而不是函数声明。注意理解这里的圆括号和上面的圆括号遇到函数时的表现是不一样的,也就是说。
- 当圆括号出现在匿名函数的末尾想要调用函数时,它会默认将函数当成是函数声明。
- 当圆括号包裹函数时,它会默认将函数作为表达式去解析,而不是函数声明。
1 | // 这两种模式都可以被用来立即调用一个函数表达式,利用函数的执行来创造私有变量 |
关于括号的重要笔记
在一些情况下,当额外的带着歧义的括号围绕在函数表达式周围是没有必要的(因为这时候的括号已经将其作为一个表达式去表达),但当括号用于调用函数表达式时,这仍然是一个好主意。
这样的括号指明函数表达式将会被立即调用,并且变量将会储存函数的结果,而不是函数本身。当这是一个非常长的函数表达式时,这可以节约比人阅读你代码的时间,不用滚到页面底部去看这个函数是否被调用。
作为规则,当你书写清楚明晰的代码时,有必要阻止 JavaScript 抛出错误的,同样也有必要阻止其他开发者对你抛出错误 WTFError!
保存闭包的状态
就像当函数通过他们的名字被调用时,参数会被传递,而当函数表达式被立即调用时,参数也会被传递。一个立即调用的函数表达式可以用来锁定值并且有效的保存此时的状态,因为任何定义在一个函数内的函数都可以使用外面函数传递进来的参数和变量(这种关系被叫做闭包)。
1 | // 它的运行原理可能并不像你想的那样,因为`i`的值从来没有被锁定。 |
记住,在这最后两个例子里,lockedInIndex可以没有任何问题的访问i,但是作为函数的参数使用一个不同的命名标识符可以使概念更加容易的被解释。
立即执行函数一个最显著的优势是就算它没有命名或者说是匿名,函数表达式也可以在没有使用标识符的情况下被立即调用,一个闭包也可以在没有当前变量污染的情况下被使用。
自执行匿名函数(“Self-executing anonymous function”)有什么问题呢?
你看到它已经被提到好几次了,但是它仍然不是那么清楚的被解释,我提议将术语改成“Immediately-Invoked Function Expression”,或者,IIFE,如果你喜欢缩写的话。
什么是Immediately-Invoked Function Expression呢?它使一个被立即调用的函数表达式。就像引导你去调用的函数表达式。
我想Javascript社区的成员应该可以在他们的文章里或者陈述里接受术语,Immediately-Invoked Function Expression和 IIFE,因为我感觉这样更容易让这个概念被理解,并且术语”self-executing anonymous function”真的也不够精确。
1 | // 下面是个自执行函数,递归的调用自己本身 |
希望上面的例子可以让你更加清楚的知道术语’self-executing’是有一些误导的,因为他并不是执行自己的函数,尽管函数已经被执行。同样的,匿名函数也没用必要特别指出,因为,Immediately Invoked Function Expression,既可以是命名函数也可以匿名函数。
最后:模块模式
当我调用函数表达式时,如果我不至少一次的提醒我自己关于模块模式,我便很可能会忽略它。
如果你并不熟悉 JavaScript 里的模块模式,它和我下面的例子很像,但是返回值用对象代替了函数。
1 | var counter = (function(){ |
模块模式方法不仅相当的厉害而且简单。非常少的代码,你可以有效的利用与方法和属性相关的命名,在一个对象里,组织全部的模块代码即最小化了全局变量的污染也创造了使用变量。