IIFE

5 分钟
79 阅读
IIFE

IIFE(Immediately Invoked Function Expression)立即执行函数表达式

什么是 IIFE

IIFE 就是定义完立即执行的函数

语法:

javascript 复制代码
(function() {
  // 代码
})()

// 或者
(function() {
  // 代码
}())

原理

JavaScript 有两种函数声明方式的执行区别:

javascript 复制代码
// 1. 函数声明 - 不会立即执行
function foo() {
  console.log('hello')
}

// 2. 函数表达式 - 也不会立即执行
const foo = function() {
  console.log('hello')
}

// 3. IIFE - 立即执行
(function() {
  console.log('hello')
})()  // 立即输出 'hello'

为什么要用括号包裹?

javascript 复制代码
// ❌ 错误 - 语法错误
function() { console.log('hello') }()

// ✅ 正确 - 括号让它变成表达式
(function() { console.log('hello') })()

括号 () 把函数声明变成了函数表达式,然后最后的 () 立即调用它。

核心作用:创建独立作用域

javascript 复制代码
// 没有 IIFE - 变量污染全局
var name = 'global'
const age = 25

console.log(window.name)  // 'global'
console.log(window.age)   // undefined (const/let 不会挂到 window)

// 使用 IIFE - 变量隔离
(function() {
  var name = 'local'
  const age = 25
  console.log(name)  // 'local'
})()

console.log(name)  // 'global' - 外部访问不到 IIFE 内的变量

使用场景

1. 避免变量污染全局作用域(你的场景)

javascript 复制代码
// 场景:多次执行同一段脚本
// ❌ 错误 - 第二次执行会报错
const config = { api: 'xxx' }
init(config)

// 再次执行同样代码
const config = { api: 'yyy' }  // ❌ SyntaxError: Identifier 'config' has already been declared

// ✅ 正确 - 使用 IIFE
(function() {
  const config = { api: 'xxx' }
  init(config)
})()

(function() {
  const config = { api: 'yyy' }  // ✅ 不会冲突
  init(config)
})()

2. 模块化(ES6 之前)

javascript 复制代码
// jQuery 插件的典型写法
(function($, window, document) {
  // 内部变量外部访问不到
  const privateVar = 'secret'
  
  function privateFunction() {
    console.log(privateVar)
  }
  
  // 暴露公共接口
  window.MyPlugin = {
    init: function() {
      privateFunction()
    }
  }
})(jQuery, window, document)

// 使用
MyPlugin.init()  // 可以访问
console.log(privateVar)  // undefined - 访问不到

3. 循环中的闭包陷阱

javascript 复制代码
// ❌ 错误 - 都会打印 5
for (var i = 0; i < 5; i++) {
  setTimeout(function() {
    console.log(i)  // 5, 5, 5, 5, 5
  }, 100)
}

// ✅ 正确 - 使用 IIFE 保存每次的 i
for (var i = 0; i < 5; i++) {
  (function(j) {
    setTimeout(function() {
      console.log(j)  // 0, 1, 2, 3, 4
    }, 100)
  })(i)
}

// 或者用 let(ES6 方案)
for (let i = 0; i < 5; i++) {
  setTimeout(function() {
    console.log(i)  // 0, 1, 2, 3, 4
  }, 100)
}

4. 传递参数并重命名

javascript 复制代码
// 避免 undefined 被重写(老版本 JS 的坑)
(function(window, undefined) {
  console.log(undefined)  // 真正的 undefined
})(window)

// 简化全局对象引用
(function($) {
  $('#button').click(function() {
    // 可以放心用 $ 而不担心冲突
  })
})(jQuery)

5. 单例模式

javascript 复制代码
const Singleton = (function() {
  let instance = null
  
  function createInstance() {
    return {
      name: 'singleton',
      getData: function() {
        return 'data'
      }
    }
  }
  
  return {
    getInstance: function() {
      if (!instance) {
        instance = createInstance()
      }
      return instance
    }
  }
})()

const obj1 = Singleton.getInstance()
const obj2 = Singleton.getInstance()
console.log(obj1 === obj2)  // true - 同一个实例

6. 条件初始化

javascript 复制代码
const addEvent = (function() {
  // 只判断一次环境
  if (window.addEventListener) {
    return function(el, type, fn) {
      el.addEventListener(type, fn, false)
    }
  } else if (window.attachEvent) {
    return function(el, type, fn) {
      el.attachEvent('on' + type, fn)
    }
  }
})()

// 之后调用不需要再判断
addEvent(button, 'click', handler)

IIFE 的变体

javascript 复制代码
// 1. 箭头函数版本
(() => {
  console.log('ES6 IIFE')
})()

// 2. 带参数
(function(name) {
  console.log('Hello ' + name)
})('World')

// 3. 有返回值
const result = (function() {
  return 42
})()
console.log(result)  // 42

// 4. 命名 IIFE(用于递归或调试)
(function factorial(n) {
  return n <= 1 ? 1 : n * factorial(n - 1)
})(5)  // 120

// 5. 异步 IIFE
(async function() {
  const data = await fetch('/api')
  console.log(data)
})()

现代替代方案

ES6 Module:

javascript 复制代码
// module.js
const privateVar = 'secret'
export const publicVar = 'public'

// main.js
import { publicVar } from './module.js'
// privateVar 访问不到

Block Scope (let/const):

javascript 复制代码
{
  const temp = 'block scoped'
  console.log(temp)
}
console.log(temp)  // ReferenceError

你的场景总结

你的问题:

javascript 复制代码
// React 组件重新渲染时,script 被多次添加到 DOM
const storyblockContent1 = document.querySelector('.storyblock-content')
// 第二次渲染时报错:Identifier 'storyblockContent1' has already been declared

解决方案:

javascript 复制代码
(function() {
  const storyblockContent1 = document.querySelector('.storyblock-content')
  // 变量在函数作用域内,不会污染全局,可以重复执行
})()

性能考虑

IIFE 会创建新的作用域,但对性能影响微乎其微。现代 JS 引擎对此优化得很好。

总结

  • 本质:创建独立作用域
  • 核心用途:避免变量污染
  • 现代开发:ES6 Module 和 let/const 已经解决了大部分 IIFE 的使用场景
  • 仍然有用:动态脚本注入、第三方库集成、向后兼容

你的代码里用 IIFE 包裹是最佳实践,避免了 React 重渲染导致的变量重复声明问题。

评论

评论

发表评论