翻译: 卷帘依旧
用IIFE
写出更为安全的代码
你应该听说过IIFE
的概念吧。
IIFE
全称为Immediately Invoked Function Express
-立即执行函数,顾名思义,是在定义之后立即执行的函数。
IIFE
主要以保护变量范围著称。但这实际上意味着什么呢,以及IIFE
的实际应用包括哪些呢?
在这篇文章中你会找到以上问题的答案,一起来探究下吧。
闭包
定义在IIFE
内部的变量外界是访问不到的。换句话说,当使用let
或const
声明的变量,在块内部才能访问到。(注:块即为{}
定义的范围)
然而,有时候你会需要修改这些变量,这种情况不可避免。
怎么修改呢:
闭包大家都了解吧,闭包提供了在函数内部访问外部函数范围的能力。创建闭包只不过是在另一个函数内部定义一个函数并且对外暴露该函数。
当闭包跟IIFE
结合的时候,会有以下两种优势:
- 变量范围得到安全限制,能够避免被意外行为修改;
- 你可以在函数外部修改函数内部的变量。这听起来破坏了第一种优势,实际上并没有。因为变量并不能被直接修改,只能通过内部暴露的函数修改。这种方式是安全的。
const user = (function() {
let name = 'anonymous';
return {
getName: _ => name,
setName: newName => name = newName
};
})();
console.log(user.getName()) // anonymous
user.setName('Amy');
console.log(user.getName()); // Amy
name
是私有变量,只能在立即执行函数user
内部修改。但是因为这里我们使用了闭包,我们可以通过暴露setName()
方法,在外部修改该变量。
全局变量的别名
使用大量的JavaScript
库可能会导致冲突,因为这些库对外暴露的对象可能同名。
比如果你使用了jQuery
。我们都知道它暴露了$
作为主要对象。因为,只要在你的项目依赖中有任意库也使用了$
符号导出变量,冲突就发生了。
幸运的是,你可以通过立即执行函数
设置别名来解决这个问题:
(function ($) {
// You’re safe to use jQuery here
})(jQuery);
通过将代码包裹在IIFE
中,并将jQuery
作为参数传入,就能保证$
符号只会引用jQuery
而不是其他库。
安全的变量范围
ES6
引入了let
和const
来以一种更为安全的方式定义变量。使用var
可能会导致意外行为,因为var
的范围很容易遭到破坏。
但是如果生产环境不支持ES6
怎么办呢?或者在某些情况下你不能使用let
和const
?
不用担心。你还有IIFE
可以用,Immediately Invoked Function Expression-立即执行函数可以达到相同的目的。
(function () {
var greeting = ‘Good morning! How are you today?’;
console.log(greeting); // Good morning! How are you today?
})();
console.log(greeting); // error: Uncaught ReferenceError: greeting is not defined
正如在以上demo中见到的,在立即执行函数内部执行的,仅仅在IIFE
内有效。你无法在外部访问IIFE
内部定义的变量。
循环索引
在异步任务内执行循环可能会导致意想不到的结果。
以setTimeout()
为例:
for (var i = 0; i < 3; i++) {
setTimeout(_ => console.log(`We’re at ${i}`), 100);
}
我们期望得到:
We’re at 0
We’re at 1
We’re at 2
但实际上结果是
We’re at 3
We’re at 3
We’re at 3
为什么会这样捏?因为例子中的console.log()
语句设置在100ms
之后执行。循环在那之前就执行完毕了,也就意味着i
已经到3
了。结果,所有的console.log()
都会打印最终结果: i = 3
我们可以通过将setTimeout
放入IIFE
中,来解决这个问题:
for (var i = 0; i < 3; i++) {
(function(index) {
setTimeout(_ => console.log(`We’re at ${index}`), 100);
})(i);
}
结果是符合预期的:
We’re at 0
We’re at 1
We’re at 2
另外,这是ES6
的时代,我们可以使用let
简化代码:
for (let i = 0; i < 3; i++) {
setTimeout(_ => console.log(`We’re at ${i}`), 100);
}
总结
IIFE
是一个保障范围安全的好方法。你可以使用IIFE
预防全局变量定义的问题,给变量起别名,保护私有变量,以及避免不同库导出相同对象名称导致的冲突问题。
尽管可以使用ES6
的特性替换IIFE
,你仍然应该学习IIFE
来更明确的理解JavaScript
中的范围是如何生效的。并且,你不可能让旧项目立即适用ES6
。所以,IIFE
仍然很重要。
你学会了没,希望你能喜欢这篇文章