在讨论ECMAScript闭包之前,先来介绍下函数式编程(与ECMA-262-3 标准无关)中一些基本定义。 然而,为了更好的解释这些定义,这里还是拿ECMAScript来举例。
众所周知,在函数式语言中(ECMAScript也支持这种风格),函数即是数据。就比方说,函数可以保存在变量中,可以当参数传递给其他函数,还可以当返回值返回等等。 这类函数有特殊的名字和结构。
定义函数式参数(“Funarg”) —— 是指值为函数的参数。
如下例子:
function exampleFunc(funArg) { funArg(); } exampleFunc(function () { alert('funArg'); });
上述例子中funArg的实参是一个传递给exampleFunc的匿名函数。
反过来,接受函数式参数的函数称为 高阶函数(high-order function 简称:HOF)。还可以称作:函数式函数 或者 偏数理的叫法:操作符函数。 上述例子中,exampleFunc 就是这样的函数。
此前提到的,函数不仅可以作为参数,还可以作为返回值。这类以函数为返回值的函数称为 _带函数值的函数(functions with functional value or function valued functions)。
(function functionValued() {
return function () {
alert('returned function is called');
};
})()();//这种()直接执行的方式要熟悉。
可以以正常数据形式存在的函数(比方说:当参数传递,接受函数式参数或者以函数值返回)都称作 第一类函数(一般说第一类对象)。 在ECMAScript中,所有的函数都是第一类对象。
接受自己作为参数的函数,称为 自应用函数(auto-applicative function 或者 self-applicative function):
(function selfApplicative(funArg) { if (funArg && funArg === selfApplicative) { alert('self-applicative'); return; } selfApplicative(selfApplicative); })();
以自己为返回值的函数称为 自复制函数(auto-replicative function 或者 self-replicative function)。 通常,“自复制”这个词用在文学作品中:
(function selfReplicative() {
return selfReplicative;
})();
在函数式参数中定义的变量,在“funArg”激活时就能够访问了(因为存储上下文数据的变量对象每次在进入上下文的时候就创建出来了):
function testFn(funArg) { // 激活funArg, 本地变量localVar可访问 funArg(10); // 20 funArg(20); // 30 } testFn(function (arg) { var localVar = 10; alert(arg + localVar); });
然而,我们知道,在ECMAScript中,函数是可以封装在父函数中的,并可以使用父函数上下文的变量。 这个特性会引发 funArg问题。
FunArg问题在面向堆栈的编程语言中,函数的本地变量都是保存在堆栈上的, 每当函数激活的时候,这些变量和函数参数都会压栈到该堆栈上。
当函数返回的时候,这些参数又会从堆栈中移除。这种模型对将函数作为函数式值使用的时候有很大的限制(比方说,作为返回值从父函数中返回)。 绝大部分情况下,问题会出现在当函数有 自由变量的时候。
自由变量是指在函数中使用的,但既不是函数参数也不是函数的局部变量的变量
如下所示:
function testFn() { var localVar = 10; function innerFn(innerParam) { alert(innerParam + localVar); } return innerFn; } var someFn = testFn(); someFn(20); // 30
上述例子中,对于innerFn函数来说,localVar就属于自由变量。
对于采用 面向堆栈模型来存储局部变量的系统而言,就意味着当testFn函数调用结束后,其局部变量都会从堆栈中移除。 这样一来,当从外部对innerFn进行函数调用的时候,就会发生错误(因为localVar变量已经不存在了)。
而且,上述例子在 面向堆栈实现模型中,要想将innerFn以返回值返回根本是不可能的。 因为它也是testFn函数的局部变量,也会随着testFn的返回而移除。
还有一个函数对象问题和当系统采用动态作用域,函数作为函数参数使用的时候有关。
看如下例子:
var z = 10; function foo() { alert(z); } foo(); // 10 – 静态作用域和动态作用域情况下都是 (function () { var z = 20; foo(); // 10 – 静态作用域情况下, 20 – 动态作用域情况下 })(); // 将foo函数以参数传递情况也是一样的 (function (funArg) { var z = 30; funArg(); // 10 – 静态作用域情况下, 30 – 动态作用域情况下 })(foo);
我们看到,采用动态作用域,变量(标识符)处理是通过动态堆栈来管理的。 因此,自由变量是在当前活跃的动态链中查询的,而不是在函数创建的时候保存起来的静态作用域链中查询的。