Effective JavaScript Item 13 使用即时调用的函数表达式(IIFE)来创建局部域

本系列作为Effective JavaScript的读书笔记。

所谓的即时调用的函数表达式,这个翻译也许不太准确,它对应的英文原文是Immediately Invoked Function Expression (IIFE)。下文也使用IIFE来表达这一概念。

首先看一个程序:

function wrapElements(a) {
	var result = [], i, n;
	for (i = 0, n = a.length; i < n; i++) {
		result[i] = function() { return a[i]; };
	}
	return result;
}
var wrapped = wrapElements([10, 20, 30, 40, 50]);
var f = wrapped[0];
f(); // ?

这个程序的作用是,将传入到wrapElements中的数组的每个元素进行一次"打包"操作,将元素替换成一个返回它们自身的函数,

也许你会认为最后输出的而结果是10,但是最后的结果实际上是undefined。

会做出这种符合直觉的假设的原因是,在function() { return a[i]; };
这段代码中,一般会认为这里的i就是当前循环时使用到的变量i,那么下面的赋值就应该成立:

result[1] = function() { return a[1]; };

但是,不要忘了一个重要的事实,在以上的赋值语句中,首先会创建一个闭包。在这个闭包中引用到了其外部的变量i,回顾在Item
11我们学习到的关于闭包的一个原则,就是闭包会以引用的形式存储其外部的变量,而不是以值的形式。

所以,在循环结束之后,i的值应该是5。那么在调用wrapped[0]()时,就相当于调用:return
a[5]。很显然,这个值是不存在的,故返回undefined。

如果将上述代码换成下面这样:

function wrapElements(a) {
	var result = [];
	for (var i = 0, n = a.length; i < n; i++) {
		result[i] = function() { return a[i]; };
	}
	return result;
}
var wrapped = wrapElements([10, 20, 30, 40, 50]);
var f = wrapped[0];
f(); // ?

可以发现,变量i和n直到for循环开始的时候才会开始。那么结果是怎么样的呢?

结果还是undefined。

这是因为VariableHoisting(Item 12)的缘故。无论在一个function中的哪里声明变量,该变量的定义部分总会出现在function的开始处。

那么,如何让程序以我们期待的方式运行呢,IIFE就派上用场了:

function wrapElements(a) {
	var result = [];
	for (var i = 0, n = a.length; i < n; i++) {
		(function() {
			var j = i;
			result[i] = function() { return a[j]; };
		})();
	}
	return result;
}

在for循环中,创建了一个IIFE,将当前的循环变量i赋值给了j,然后在后面的function中返回的是a[j]。这相当使用了另外一个局部变量将外部变量当前的值给记录下来,以便将来使用。那么IIFE的价值就在于它能够克服JavaScript语言中没有块作用域(Block
Scoping)的弊端:通过创建一个匿名的闭包,将某个外部变量的当前值给保存起来。就好比将一个变量的当前值"冻结"住,供将来使用。

IIFE的另一种形式是将需要"冻结"的变量作为参数传入到IIFE中:

function wrapElements(a) {
	var result = [];
	for (var i = 0, n = a.length; i < n; i++) {
		(function(j) {
			result[i] = function() { return a[j]; };
		})(i);
	}
	return result;
}

在使用IIFE的时候,有两个注意事项:

  1. 如果在for或者while循环中使用了IIFE,那么注意不要在IIFE中包含break以及continue语句。因为break和continue只有在循环中才能够使用,否则抛出SyntaxError:
    Illegal break statement。
  2. 如果IIFE中使用了this或者arguments,那么它们的意义可能会和你想象的有出入,关于这一点,在后续的Items中会进行介绍。

总结:

  1. 闭包会存储外部变量的引用,而不是值
  2. 使用IIFE来创建局部作用域

时间: 2024-10-03 14:46:06

Effective JavaScript Item 13 使用即时调用的函数表达式(IIFE)来创建局部域的相关文章

Effective JavaScript Item 26 使用bind来进行函数的柯里化(Curry)

本系列作为Effective JavaScript的读书笔记. 在上一个Item中介绍了bind的一种用法:用来绑定this对象.但是实际上,bind含有另一种用法,就是帮助函数进行柯里化.关于柯里化,这里有一份百科可以参考: http://zh.wikipedia.org/wiki/%E6%9F%AF%E9%87%8C%E5%8C%96 但是实际上,关于柯里化只需要记住一点就够了:柯里化是把接受多个参数的函数变换成接受一个单一参数(通常是最初函数的第一个参数,但是并无限制)的函数,并且返回这个

立即调用的函数表达式---IIFE

有些人则称为“自执行的匿名函数” 在闭包中,我们经常需要使用到匿名函数,我感觉闭包就是一种匿名函数,子集. 但是直接在匿名函数后面调用函数是会出错的.比如: function () { alert("Hello IIFE"); }();// Uncaught SyntaxError: Unexpected token ( 我们期望系统立即调用这个匿名函数,但是系统会以为我们是在进行函数声明,进行函数声明需要有函数名称.而上面则没有,就会报错. 那么我们为它添加函数名咯,还会有一个小问题

Effective JavaScript Item 62 在异步调用中使用嵌套或者命名的回调函数

在一开始,理解异步程序的调用顺序会有些困难.比如,下面的程序中,starting会先被打印出来,然后才是finished: downloadAsync("file.txt", function(file) { console.log("finished"); }); console.log("starting"); downloadAsync方法在执行之后会立即返回,它只是为下载这个行为注册了一个回调函数而已. 由于JavaScript"

Effective JavaScript Item 63 注意异步调用中可能会被忽略的异常

异常处理是异步编程的一个难点. 在同步的代码中,异常可以非常easy地通过try catch语句来完毕: try { f(); g(); h(); } catch (e) { // handle any error that occurred... } 可是在异步代码中,使用一个try代码块将全部可能出现的异常都包含在内是不现实的.实际上,异步API设置不能抛出异常.由于当异常发生时,通常已经没有运行上下文供它抛出了. 全部,在异步API中一般会使用特殊的參数或者错误回调函数来表示异常信息.比方

Effective JavaScript Item 55 接受配置对象作为函数参数

接受配置对象作为函数参数 虽然保持函数接受的参数的顺序很重要,但是当函数能够接受的参数达到一定数量时,也会让用户很头疼: var alert = new Alert(100, 75, 300, 200, "Error", message, "blue", "white", "black", "error", true); 随着函数的不断重构和进化,它能够接受的参数也许会越来越多,最终就像上面的例子那样. 对

匿名函数立即调用的函数表达式 -IIFE(Immediately-Invoked Function Expression)

javascript 匿名函数有哪几种执行方式: ( function() {}() ); ( function() {} )(); [ function() {}() ]; ~ function() {}(); ! function() {}(); + function() {}(); - function() {}(); delete function() {}(); typeof function() {}(); void function() {}(); new function() {

[Effective JavaScript 笔记] 第13条:使用立即调用的函数表达式创建局部作用域

function wrapElements(a){ var res=[],i,n; for(i=0,n=a.length;i<n;i++){ res[i]=function(){return a[i]}; } return res; } var wrapped=wrapElements([10,20,30,40,50]); var f=wrapped[0]; f();//undefined 这个可以由之前的闭包来讲,res里的每个函数都是一个闭包,它们都可以访问上一个函数的作用域内的变量,所以每

第 13 条:使用立即调用的函数表达式创建局部作用域

第 13 条:使用立即调用的函数表达式创建局部作用域这段程序(Bug 程序)输出什么? function wrapElements(a) { var result = [], i, n; for (i = 0, n = a.length; i < n; i++) { result[i] = function() { return a[i]; }; } return result; } var wrapped = wrapElements([10, 20, 30, 40, 50]); var f

理解JavaScript的立即调用函数表达式(IIFE)

首先这是js的一种函数调用写法,叫立即执行函数表达式(IIFE,即immediately-invoked function expression).顾名思义IIFE可以让你的函数立即得到执行(废话). 一般来说,IIFE有以下几种用途: 1. 创建只使用一次的函数,并立即执行它. 2. 创建闭包,保存状态,隔离作用域. 3. 作为独立模块存在(例子如jQuery),防止命名冲突,命名空间注入(模块解耦). 1. 创建只使用一次的函数,并立即执行它 创建只使用一次的函数比较好理解,在需要调用函数的