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 重渲染导致的变量重复声明问题。
