理解js中的IIFE

359 阅读3分钟

1、什么是立即调用函数?它有什么特点?

Immediate Invoke Function Expression MDN 上的释义是 “一个在定义时就会立即执行的 JavaScript 函数”

示例:

;(function () {
  statements
})()

这是一个被称为 自执行匿名函数 的设计模式,主要包含两部分:

  1. 第一部分是包围在 圆括号运算符 () 里的一个匿名函数,这个匿名函数拥有独立的词法作用域。这不仅避免了外界访问此 IIFE 中的变量,而且又不会污染全局作用域;
  2. 第二部分再一次使用 () 创建了一个立即执行函数表达式。

了解了立即调用函数的定义之后,他的特点我们也可以总结出来。

  1. 定义时就会立即执行;
  2. 函数内部拥有独立的作用域,外界无法访问 IIFE 中的变量。

2、IIFE 有哪些写法

为什么要将匿名函数放在括号中?拿出来不可以吗?我们来试一试。

function(){
  console.log(123)
}() // err: Uncaught SyntaxError: Function statements require a function name

加上函数名称

function foo(){
  console.log(123)
}() // err: Uncaught SyntaxError: Unexpected token ')'

又出现了另一个错误 两种方式都出现了语法错误,我们用 AST 解析工具来看一下,上面两种 写法都被识别为了 FunctionDeclaration + ExpressionStatement c0cd47570346a516ea9032b552fea7c.png

正确的写法识别为了 FunctionExpression CallExpression

feee82435064908cca05e60ed5f59b0.png

产生语法错误的原因是,function 这个关键字,既可以当做声明语句,也可以当做表达式

//语句
function fn() {}

//表达式
var fn = function () {}

为了避免歧义,JS 引擎规定,如果 function 出现在行首,一律解析成声明语句。所以上面两种写法的错误分别是

  1. 声明函数时缺少函数名;
  2. 分组操作符() 中缺少表达式。

了解了上述问题,我们就可以写出很多种 IIFE 的写法了。

// 常用写法
;(function () {
  console.log(123)
})()
;(function () {
  console.log(123)
})()
void (function () {
  console.log(123)()
})()
// 带返回值和参数
var result = (function (param) {
  console.log(param)
  return param + 1
})(123)
// 使用箭头函数的 IIFE
;(() => {
  console.log(123)
})()

总之要让编译器将function 识别为表达式而不是函数声明。

3、常见使用场景

3.1、隔离作用域

IIFE 最常见的功能,就是隔离作用域,在 ES6 之前 JS 原生也没有块级作用域的概念,所以需要函数作用域来模拟。像一些常见的库 jquery vue 代码就是在 IIFE 中执行的。 07114de008da7183b4c019790fbf2b2.png 优点如下。

  1. 避免全局变量的污染:IIFE 可以创建一个新的作用域,防止代码中的变量污染全局作用域,避免了命名冲突和变量重复定义的问题。
  2. 模块化开发:IIFE 可以模拟模块化开发,将代码拆分成独立的模块,使得代码更易于管理和维护。
  3. 提高代码的可读性:IIFE 可以将代码逻辑封装在函数中,让代码更加清晰,易于理解和阅读。
  4. 代码安全性更高:IIFE 可以保护代码的私有性,防止恶意代码或攻击者对代码的非法访问或篡改。

3.2 用闭包保存状态

for循环中

for (var i = 0; i < 5; i++) {
  ;(function (index) {
    setTimeout(function () {
      console.log(index)
    }, 1000)
  })(i)
}
// 0
// 1
// 2
// 3
// 4

3.3 惰性函数

函数被调用时,仅仅进行一次函数定义的判断,而不是每次都进行判断,从而优化了代码性能。加强了代码可读性。

var addEvent = (function () {
  if (typeof window.addEventListener === 'function') {
    return function (element, type, handler) {
      element.addEventListener(type, handler, false)
    }
  } else if (typeof document.attachEvent === 'function') {
    return function (element, type, handler) {
      element.attachEvent('on' + type, handler)
    }
  } else {
    return function (element, type, handler) {
      element['on' + type] = handler
    }
  }
})()