立即执行函数(IIFE)
立即执行函数(IIFE)
看到这里,相信你一定迫不及待地想知道究竟如何做了吧,其实很简单,只需要用括号全部括起来即可,比如下面这样:
(function(){ /* code */ }());
为什么这样就能立即执行并且不报错呢?因为在javascript里,括号内部不能包含语句,当解析器对代码进行解释的时候,先碰到了(),然后碰到function关键字就会自动将()里面的代码识别为函数表达式而不是函数声明。
而立即执行函数并非只有上面的一种写法,写法真是五花八门:

// 最常用的两种写法
(function(){ /* code */ }()); // 老道推荐写法
(function(){ /* code */ })(); // 当然这种也可以 // 括号和JS的一些操作符(如 = && || ,等)可以在函数表达式和函数声明上消除歧义
// 如下代码中,解析器已经知道一个是表达式了,于是也会把另一个默认为表达式
// 但是两者交换则会报错
var i = function(){ return 10; }();
true && function(){ /* code */ }();
0, function(){ /* code */ }(); // 如果你不怕代码晦涩难读,也可以选择一元运算符
!function(){ /* code */ }();
~function(){ /* code */ }();
-function(){ /* code */ }();
+function(){ /* code */ }(); // 你也可以这样
new function(){ /* code */ }
new function(){ /* code */ }() // 带参数

- 无论何时,给立即执行函数加上括号是个好习惯
通过以上的介绍,我们大概了解通过()可以使得一个函数表达式立即执行。
有的时候,我们实际上不需要使用()使之变成一个函数表达式,啥意思?比如下面这行代码,其实不加上()也不会保错:
var i = function(){ return 10; }();
但是我们依然推荐加上():
var i = (function(){ return 10; }());
为什么?因为我们在阅读代码的时候,如果function内部代码量庞大,我们不得不滚动到最后去查看function(){}后是否带有()来确定i值是个function还是function内部的返回值。所以为了代码的可读性,请尽量加上()无论是否已经是表达式。
- 立即执行函数与闭包的暧昧关系
立即执行函数能配合闭包保存状态。
像普通的函数传参一样,立即执行函数也能传参数。如果在函数内部再定义一个函数,而里面的那个函数能引用外部的变量和参数(闭包),利用这一点,我们能使用立即执行函数锁住变量保存状态。

// 并不会像你想象那样的执行,因为i的值没有被锁住
// 当我们点击链接的时候,其实for循环已经执行完了
// 于是在点击的时候i的值其实已经是elems.length了
var elems = document.getElementsByTagName( 'a' ); for ( var i = 0; i < elems.length; i++ ) { elems[ i ].addEventListener( 'click', function(e){
e.preventDefault();
alert( 'I am link #' + i );
}, 'false' ); } // 这次我们得到了想要的结果
// 因为在立即执行函数内部,i的值传给了lockedIndex,并且被锁在内存中
// 尽管for循环结束后i的值已经改变,但是立即执行函数内部lockedIndex的值并不会改变
var elems = document.getElementsByTagName( 'a' ); for ( var i = 0; i < elems.length; i++ ) { (function( lockedInIndex ){ elems[ i ].addEventListener( 'click', function(e){
e.preventDefault();
alert( 'I am link #' + lockedInIndex );
}, 'false' ); })( i ); } // 你也可以这样,但是毫无疑问上面的代码更具有可读性
var elems = document.getElementsByTagName( 'a' ); for ( var i = 0; i < elems.length; i++ ) { elems[ i ].addEventListener( 'click', (function( lockedInIndex ){
return function(e){
e.preventDefault();
alert( 'I am link #' + lockedInIndex );
};
})( i ), 'false' ); }

其实上面代码的lockedIndex也可以换成i,因为两个i是在不同的作用域里,所以不会互相干扰,但是写成不同的名字更好解释。以上便是立即执行函数+闭包的作用。
- 我为什么更愿意称它是“立即执行函数”而不是“自执行函数”
IIFE的称谓在现在似乎已经得到了广泛推广(不知道是不是原文作者的功劳?),而原文写于10年,似乎当时流行的称呼是自执行函数(Self-executing anonymous function),接下去作者开始为了说明立即执行函数的称呼好于自执行函数的称呼开始据理力争,有点咬文嚼字,不过也蛮有意思的,我们来看看作者说了些什么。

// 这是一个自执行函数,函数内部执行的是自己,递归调用
function foo() { foo(); } // 这是一个自执行匿名函数,因为它没有函数名
// 所以如果要递归调用自己的话必须用arguments.callee
var foo = function() { arguments.callee(); }; // 这可能也算是个自执行匿名函数,但仅仅是foo标志引用它自身
// 如果你将foo改变成其它的,你将得到一个used-to-self-execute匿名函数
var foo = function() { foo(); }; // 有些人叫它自执行匿名函数,尽管它没有执行自己,只是立即执行而已
(function(){ /* code */ }()); // 给函数表达式添加了标志名称,可以方便debug
// 但是一旦添加了标志名称,这个函数就不再是匿名的了
(function foo(){ /* code */ }()); // 立即执行函数也可以自执行,不过不常用罢了
(function(){ arguments.callee(); }());
(function foo(){ foo(); }());

我的理解是作者认为自执行函数是函数内部调用自己(递归调用),而立即执行函数就如字面意思,该函数立即执行即可。其实现在也不用去管它了,就叫IIFE好了。
- 最后的旁白:模块模式
立即执行函数在模块化中也大有用处。用立即执行函数处理模块化可以减少全局变量造成的空间污染,构造更多的私有变量。

// 创建一个立即执行的匿名函数
// 该函数返回一个对象,包含你要暴露的属性
// 如下代码如果不使用立即执行函数,就会多一个属性i
// 如果有了属性i,我们就能调用counter.i改变i的值
// 对我们来说这种不确定的因素越少越好 var counter = (function(){
var i = 0; return {
get: function(){
return i;
},
set: function( val ){
i = val;
},
increment: function() {
return ++i;
}
};
}()); // counter其实是一个对象 counter.get(); // 0
counter.set( 3 );
counter.increment(); // 4
counter.increment(); // 5 counter.i; // undefined i并不是counter的属性
i; // ReferenceError: i is not defined (函数内部的是局部变量)

扩展阅读
如果你愿意了解更多内容,特别是关于函数和模块模式的内容,建议阅读下列文章。
- ECMA-262-3 in detail. Chapter 5. Functions. - Dmitry A. Soshnikov
- Functions and function scope - Mozilla Developer Network
- Named function expressions - Juriy “kangax” Zaytsev
- JavaScript Module Pattern: In-Depth - Ben Cherry
- Closures explained with JavaScript - Nick Morgan
某坑
在实际开发中,关于 IIFE 遇到了一个坑,即 IIFE 中使用 JSONP,很显然 JSONP 中的函数调用是获取不到匿名函数中定义的函数的:

!function() {
// 回调函数定义在匿名函数内,JSONP回调找不到该函数
function callback() {
//...
} T.getScript('..'); // 获取JSONP接口
}();

不仅仅是在 IIFE 中,如果是这样,也会出错:

// 为了封装,把一系列调用写在一个函数内
function fn() { // 设置回调
function callback() {
//...
} T.getScript('..'); // JSONP
} fn();

很显然是一样的道理,所以在用 JSONP 的时候,特别要注意它的回调函数定义必须是全局的,可以将回调函数手动设置为 window.callback() 的形式。
程序员都应该学点算法:https://github.com/hanzichi/leetcode
了解博主韩子迟:http://www.cnblogs.com/zichi/p/about.html
GitHub:https://github.com/hanzichi Follow 楼主给楼主更多写作的动力~
立即执行函数(IIFE)的更多相关文章
- 立即执行函数(IIFE)的理解与运用
作为JavaScript的常用语法,立即执行函数IIFE(Immediately-Invoked Function Expression)是值得我们认真去学习探究的. 一.创建函数的两种方式 我们先从 ...
- jacascript 立即执行函数(IIFE)与闭包
前言:这是笔者学习之后自己的理解与整理.如果有错误或者疑问的地方,请大家指正,我会持续更新! 一直没搞清楚立即执行函数和闭包之间的关系,总结一下: 闭包有很多种理解:访问不到内部作用域,函数就是这样, ...
- 立即执行函数 IIFE
立即执行函数表达式IIFE(Immediately-invoked function expression)我们知道,在javascript(ES5)中,是没有块级作用域的概念的.看一个例子 for ...
- JS立即执行函数表达式(IIFE)
原文为 http://benalman.com/news/2010/11/immediately-invoked-function-expression/#iife ----------------- ...
- 立即执行函数表达式(IIFE)
原文地址:benalman.com/news/2010/11/immediately-invoked-function-expression/ 译者:nzbin 也许你还没有注意到,我是一个对术语比较 ...
- 使用"立即执行函数"(Immediately-Invoked Function Expression,IIFE)
一.原始写法 模块就是实现特定功能的一组方法. 只要把不同的函数(以及记录状态的变量)简单地放在一起,就算是一个模块. function m1(){ //... } function m2(){ // ...
- (译)详解javascript立即执行函数表达式(IIFE)
写在前面 这是一篇译文,原文:Immediately-Invoked Function Expression (IIFE) 原文是一篇很经典的讲解IIFE的文章,很适合收藏.本文虽然是译文,但是直译的 ...
- 基础-函数3(IIFE立即执行函数)
参考链接: http://benalman.com/news/2010/11/immediately-invoked-function-expression/#iife http://segmentf ...
- IIFE(立即执行函数表达式)
我们经常会看到这样的写法: ;(fuction () { // do something })() 这就是一个简单的IIFE(立即执行函数表达式,immediately-invoked functio ...
- javascript模块化编程-详解立即执行函数表达式IIFE
一.IIFE解释 全拼Imdiately Invoked Function Expression,立即执行的函数表达式. 像如下的代码所示,就是一个匿名立即执行函数: (function(windo ...
随机推荐
- Sql日期时间格式转换(转 子夜.)
sql server2000中使用convert来取得datetime数据类型样式(全) 日期数据格式的处理,两个示例: CONVERT(varchar(16), 时间一, 20) 结果:2007-0 ...
- Redis数据结构以及Strings型操作
Redis数据结构图: Strings型 <String key,String value>: keys * 查看所有key get 获取key的value值 append 向key对 ...
- RDD算子、RDD依赖关系
RDD:弹性分布式数据集, 是分布式内存的一个抽象概念 RDD:1.一个分区的集合, 2.是计算每个分区的函数 , 3.RDD之间有依赖关系 4.一个对于key-value的RDD的Partit ...
- 1 - JVM随笔分类(java虚拟机的内存区域分配(一个不断记录和推翻以及再记录的一个过程))
java虚拟机的内存区域分配 在JVM运行时,类加载器ClassLoader在加载到类的字节码后,交由jvm的执行引擎处理, 执行过程中需要空间来存储数据(类似于Cpu及主存),此时的这段空间的分 ...
- Java并发之(2):线程通信wait/notify(TIJ_21_5)
简介: java中线程间同步的最基本的方式就是使用wait()¬ify()¬ifyAll(),它们是线程间的握手机制.除了上述方法,java5还在java.util.con ...
- SQL语句Not IN优化方案
总结网友们在CSDN社区上对于not in的优化策略,整理如下,备查. select * from emp where emp_no not in (select emp_no from emp ...
- MongoDB快速入门学习笔记5 MongoDB的文档修改操作
db.集合名称.update({query},{update},upsert, multi})query:过滤条件update:修改内容upsert:如果不存在查询条件查出的记录,是否插入一条数据,默 ...
- 如何使用 JSX 构建 Gutenberg 块
本教程将介绍使用 JSX 构建自定义块所需的步骤. 由于浏览器不支持 JSX 和 ES6,因此我们需要将代码编译后才能在浏览器中运行. 我们不需要手动编译代码,因为有些工具可以为我们自动执行此过程. ...
- PostgreSQL 安装完成后的一些配置
设置数据目录,一般使用环境变量 PGDATA 指向数据目录的根目录.目录的初始化是使用 initdb 来完成的,-D 后面加上自定义目录的路径 initdb -D /tmp/testpostgres2 ...
- linux实用命令-待补充
- du 查看目录大小 - du -h 带有单位显示目录信息 - df 查看磁盘大小 - df -h 带有单位显示磁盘信息 - netstat 显示网络状态信息 - 清除僵尸进程 ps -eal | ...