关于立即执行函数(IIFE)的实用建议

1,271 阅读3分钟

翻译: 卷帘依旧

原文地址: javascript.plainenglish.io/4-practical…

IIFE写出更为安全的代码

你应该听说过IIFE的概念吧。

IIFE全称为Immediately Invoked Function Express-立即执行函数,顾名思义,是在定义之后立即执行的函数。

IIFE主要以保护变量范围著称。但这实际上意味着什么呢,以及IIFE的实际应用包括哪些呢?

在这篇文章中你会找到以上问题的答案,一起来探究下吧。

闭包

定义在IIFE内部的变量外界是访问不到的。换句话说,当使用letconst声明的变量,在块内部才能访问到。(注:块即为{}定义的范围)

然而,有时候你会需要修改这些变量,这种情况不可避免。

怎么修改呢:

闭包大家都了解吧,闭包提供了在函数内部访问外部函数范围的能力。创建闭包只不过是在另一个函数内部定义一个函数并且对外暴露该函数。

当闭包跟IIFE结合的时候,会有以下两种优势:

  1. 变量范围得到安全限制,能够避免被意外行为修改;
  2. 你可以在函数外部修改函数内部的变量。这听起来破坏了第一种优势,实际上并没有。因为变量并不能被直接修改,只能通过内部暴露的函数修改。这种方式是安全的。
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引入了letconst来以一种更为安全的方式定义变量。使用var可能会导致意外行为,因为var的范围很容易遭到破坏。

但是如果生产环境不支持ES6怎么办呢?或者在某些情况下你不能使用letconst?

不用担心。你还有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仍然很重要。

你学会了没,希望你能喜欢这篇文章