深入理解JavaScript系列(16) 闭包(Closures)(4)


var data = [];

for (var k = 0; k < 3; k++) {
(data[k] = function () {
alert(arguments.callee.x);
}).x = k; // 将k作为函数的一个属性
}

// 结果也是对的
data[0](); // 0
data[1](); // 1
data[2](); // 2


Funarg和return
另外一个特性是从闭包中返回。在ECMAScript中,闭包中的返回语句会将控制流返回给调用上下文(调用者)。而在其他语言中,比如,Ruby,有很多中形式的闭包,相应的处理闭包返回也都不同,下面几种方式都是可能的:可能直接返回给调用者,或者在某些情况下——直接从上下文退出。

ECMAScript标准的退出行为如下:

复制代码 代码如下:


function getElement() {

[1, 2, 3].forEach(function (element) {

if (element % 2 == 0) {
// 返回给函数"forEach"函数
// 而不是返回给getElement函数
alert('found: ' + element); // found: 2
return element;
}

});

return null;
}


然而,在ECMAScript中通过try catch可以实现如下效果:

复制代码 代码如下:


var $break = {};

function getElement() {

try {

[1, 2, 3].forEach(function (element) {

if (element % 2 == 0) {
// // 从getElement中"返回"
alert('found: ' + element); // found: 2
$break.data = element;
throw $break;
}

});

} catch (e) {
if (e == $break) {
return $break.data;
}
}

return null;
}

alert(getElement()); // 2


理论版本
这里说明一下,开发人员经常错误将闭包简化理解成从父上下文中返回内部函数,甚至理解成只有匿名函数才能是闭包。

再说一下,因为作用域链,使得所有的函数都是闭包(与函数类型无关: 匿名函数,FE,NFE,FD都是闭包)。
这里只有一类函数除外,那就是通过Function构造器创建的函数,因为其[[Scope]]只包含全局对象。

为了更好的澄清该问题,我们对ECMAScript中的闭包给出2个正确的版本定义:

ECMAScript中,闭包指的是:

从理论角度:所有的函数。因为它们都在创建的时候就将上层上下文的数据保存起来了。哪怕是简单的全局变量也是如此,因为函数中访问全局变量就相当于是在访问自由变量,这个时候使用最外层的作用域。
从实践角度:以下函数才算是闭包:
即使创建它的上下文已经销毁,它仍然存在(比如,内部函数从父函数中返回)
在代码中引用了自由变量
闭包用法实战
实际使用的时候,闭包可以创建出非常优雅的设计,允许对funarg上定义的多种计算方式进行定制。如下就是数组排序的例子,它接受一个排序条件函数作为参数:

复制代码 代码如下:


[1, 2, 3].sort(function (a, b) {
... // 排序条件
});


同样的例子还有,数组的map方法是根据函数中定义的条件将原数组映射到一个新的数组中:

复制代码 代码如下:


[1, 2, 3].map(function (element) {
return element * 2;
}); // [2, 4, 6]


使用函数式参数,可以很方便的实现一个搜索方法,并且可以支持无限制的搜索条件:

复制代码 代码如下:


someCollection.find(function (element) {
return element.someProperty == 'searchCondition';
});


还有应用函数,比如常见的forEach方法,将函数应用到每个数组元素:

复制代码 代码如下:


[1, 2, 3].forEach(function (element) {
if (element % 2 != 0) {
alert(element);
}
}); // 1, 3


顺便提下,函数对象的 apply 和 call方法,在函数式编程中也可以用作应用函数。 apply和call已经在讨论“this”的时候介绍过了;这里,我们将它们看作是应用函数 —— 应用到参数中的函数(在apply中是参数列表,在call中是独立的参数):

复制代码 代码如下:


(function () {
alert([].join.call(arguments, ';')); // 1;2;3
}).apply(this, [1, 2, 3]);


闭包还有另外一个非常重要的应用 —— 延迟调用:

复制代码 代码如下:


var a = 10;
setTimeout(function () {
alert(a); // 10, after one second
}, 1000);


还有回调函数

复制代码 代码如下:

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wdjzfz.html