在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ HTML/ 閉包(Closures)
代碼復(fù)用模式(避免篇)
S.O.L.I.D 五大原則之接口隔離原則 ISP
設(shè)計(jì)模式之狀態(tài)模式
JavaScript 核心(晉級(jí)高手必讀篇)
設(shè)計(jì)模式之建造者模式
JavaScript 與 DOM(上)——也適用于新手
設(shè)計(jì)模式之中介者模式
設(shè)計(jì)模式之裝飾者模式
設(shè)計(jì)模式之模板方法
設(shè)計(jì)模式之外觀模式
強(qiáng)大的原型和原型鏈
設(shè)計(jì)模式之構(gòu)造函數(shù)模式
揭秘命名函數(shù)表達(dá)式
深入理解J avaScript 系列(結(jié)局篇)
執(zhí)行上下文(Execution Contexts)
函數(shù)(Functions)
《你真懂 JavaScript 嗎?》答案詳解
設(shè)計(jì)模式之適配器模式
設(shè)計(jì)模式之組合模式
設(shè)計(jì)模式之命令模式
S.O.L.I.D 五大原則之單一職責(zé) SRP
編寫高質(zhì)量 JavaScript 代碼的基本要點(diǎn)
求值策略
閉包(Closures)
對象創(chuàng)建模式(上篇)
This? Yes,this!
設(shè)計(jì)模式之代理模式
變量對象(Variable Object)
S.O.L.I.D 五大原則之里氏替換原則 LSP
面向?qū)ο缶幊讨话憷碚?/span>
設(shè)計(jì)模式之單例模式
Function 模式(上篇)
S.O.L.I.D 五大原則之依賴倒置原則 DIP
設(shè)計(jì)模式之迭代器模式
立即調(diào)用的函數(shù)表達(dá)式
設(shè)計(jì)模式之享元模式
設(shè)計(jì)模式之原型模式
根本沒有“JSON 對象”這回事!
JavaScript 與 DOM(下)
面向?qū)ο缶幊讨?ECMAScript 實(shí)現(xiàn)
全面解析 Module 模式
對象創(chuàng)建模式(下篇)
設(shè)計(jì)模式之職責(zé)鏈模式
S.O.L.I.D 五大原則之開閉原則 OCP
設(shè)計(jì)模式之橋接模式
設(shè)計(jì)模式之策略模式
設(shè)計(jì)模式之觀察者模式
代碼復(fù)用模式(推薦篇)
作用域鏈(Scope Chain)
Function 模式(下篇)
設(shè)計(jì)模式之工廠模式

閉包(Closures)

介紹

本章我們將介紹在 JavaScript 里大家經(jīng)常來討論的話題 —— 閉包(closure)。閉包其實(shí)大家都已經(jīng)談爛了。盡管如此,這里還是要試著從理論角度來討論下閉包,看看 ECMAScript 中的閉包內(nèi)部究竟是如何工作的。

正如在前面的文章中提到的,這些文章都是系列文章,相互之間都是有關(guān)聯(lián)的。因此,為了更好的理解本文要介紹的內(nèi)容,建議先去閱讀第 14 章作用域鏈和第 12 章變量對象。

概論

在直接討論 ECMAScript 閉包之前,還是有必要來看一下函數(shù)式編程中一些基本定義。

眾所周知,在函數(shù)式語言中(ECMAScript也支持這種風(fēng)格),函數(shù)即是數(shù)據(jù)。就比方說,函數(shù)可以賦值給變量,可以當(dāng)參數(shù)傳遞給其他函數(shù),還可以從函數(shù)里返回等等。這類函數(shù)有特殊的名字和結(jié)構(gòu)。

定義

A functional argument (“Funarg”) — is an argument which value is a function.
函數(shù)式參數(shù)(“Funarg”) —— 是指值為函數(shù)的參數(shù)。

例子:

function exampleFunc(funArg) {
  funArg();
}
exampleFunc(function () {
  alert('funArg');
});

上述例子中 funarg 的實(shí)際參數(shù)其實(shí)是傳遞給 exampleFunc 的匿名函數(shù)。

反過來,接受函數(shù)式參數(shù)的函數(shù)稱為高階函數(shù)(high-order function 簡稱:HOF)。還可以稱作:函數(shù)式函數(shù)或者偏數(shù)理或操作符。上述例子中,exampleFunc 就是這樣的函數(shù)。

此前提到的,函數(shù)不僅可以作為參數(shù),還可以作為返回值。這類以函數(shù)為返回值的函數(shù)稱為帶函數(shù)值的函數(shù)(functions with functional value or function valued functions)。

(function functionValued() {
  return function () {
    alert('returned function is called');
  };
})()();

可以以正常數(shù)據(jù)形式存在的函數(shù)(比方說:當(dāng)參數(shù)傳遞,接受函數(shù)式參數(shù)或者以函數(shù)值返回)都稱作 第一類函數(shù)(一般說第一類對象)。在 ECMAScript 中,所有的函數(shù)都是第一類對象。

函數(shù)可以作為正常數(shù)據(jù)存在(例如:當(dāng)參數(shù)傳遞,接受函數(shù)式參數(shù)或者以函數(shù)值返回)都稱作第一類函數(shù)(一般說第一類對象)。

在 ECMAScript 中,所有的函數(shù)都是第一類對象。

接受自己作為參數(shù)的函數(shù),稱為自應(yīng)用函數(shù)(auto-applicative function 或者 self-applicative function):

(function selfApplicative(funArg) {
  if (funArg && funArg === selfApplicative) {
    alert('self-applicative');
    return;
  }
  selfApplicative(selfApplicative);
})();

以自己為返回值的函數(shù)稱為自復(fù)制函數(shù)(auto-replicative function 或者 self-replicative function)。通常,“自復(fù)制”這個(gè)詞用在文學(xué)作品中:

(function selfReplicative() {
  return selfReplicative;
})();

自復(fù)制函數(shù)的其中一個(gè)比較有意思的模式是讓僅接受集合的一個(gè)項(xiàng)作為參數(shù)來接受從而代替接受集合本身。

// 接受集合的函數(shù)
function registerModes(modes) {
  modes.forEach(registerMode, modes);
}
// 用法
registerModes(['roster', 'accounts', 'groups']);
// 自復(fù)制函數(shù)的聲明
function modes(mode) {
  registerMode(mode); // 注冊一個(gè)mode
  return modes; // 返回函數(shù)自身
}
// 用法,modes鏈?zhǔn)秸{(diào)用
modes('roster')('accounts')('groups')
//有點(diǎn)類似:jQueryObject.addClass("a").toggle().removClass("b")

但直接傳集合用起來相對來說,比較有效并且直觀。

在函數(shù)式參數(shù)中定義的變量,在“funarg”激活時(shí)就能夠訪問了(因?yàn)榇鎯?chǔ)上下文數(shù)據(jù)的變量對象每次在進(jìn)入上下文的時(shí)候就創(chuàng)建出來了):

function testFn(funArg) {
  // funarg激活時(shí), 局部變量localVar可以訪問了
  funArg(10); // 20
  funArg(20); // 30
}
testFn(function (arg) {
  var localVar = 10;
  alert(arg + localVar);
});

然而,我們從第 14 章知道,在 ECMAScript 中,函數(shù)是可以封裝在父函數(shù)中的,并可以使用父函數(shù)上下文的變量。這個(gè)特性會(huì)引發(fā) funarg 問題。

Funarg 問題

面向堆棧的編程語言中,函數(shù)的局部變量都是保存在棧上的,每當(dāng)函數(shù)激活的時(shí)候,這些變量和函數(shù)參數(shù)都會(huì)壓入到該堆棧上。

當(dāng)函數(shù)返回的時(shí)候,這些參數(shù)又會(huì)從棧中移除。這種模型對將函數(shù)作為函數(shù)式值使用的時(shí)候有很大的限制(比方說,作為返回值從父函數(shù)中返回)。絕大部分情況下,問題會(huì)出現(xiàn)在當(dāng)函數(shù)有自由變量的時(shí)候。

自由變量是指在函數(shù)中使用的,但既不是函數(shù)參數(shù)也不是函數(shù)的局部變量的變量

例子:

function testFn() {
  var localVar = 10;
  function innerFn(innerParam) {
    alert(innerParam + localVar);
  }
  return innerFn;
}
var someFn = testFn();
someFn(20); // 30

上述例子中,對于 innerFn 函數(shù)來說,localVar 就屬于自由變量。

對于采用面向棧模型來存儲(chǔ)局部變量的系統(tǒng)而言,就意味著當(dāng) testFn 函數(shù)調(diào)用結(jié)束后,其局部變量都會(huì)從堆棧中移除。這樣一來,當(dāng)從外部對 innerFn 進(jìn)行函數(shù)調(diào)用的時(shí)候,就會(huì)發(fā)生錯(cuò)誤(因?yàn)?localVar 變量已經(jīng)不存在了)。

而且,上述例子在面向棧實(shí)現(xiàn)模型中,要想將 innerFn 以返回值返回根本是不可能的。因?yàn)樗彩?testFn 函數(shù)的局部變量,也會(huì)隨著 testFn 的返回而移除。

還有一個(gè)問題是當(dāng)系統(tǒng)采用動(dòng)態(tài)作用域,函數(shù)作為函數(shù)參數(shù)使用的時(shí)候有關(guān)。

看如下例子(偽代碼):

var z = 10;
function foo() {
  alert(z);
}
foo(); // 10 – 使用靜態(tài)和動(dòng)態(tài)作用域的時(shí)候
(function () {
  var z = 20;
  foo(); // 10 – 使用靜態(tài)作用域, 20 – 使用動(dòng)態(tài)作用域
})();
// 將foo作為參數(shù)的時(shí)候是一樣的
(function (funArg) {
  var z = 30;
  funArg(); // 10 – 靜態(tài)作用域, 30 – 動(dòng)態(tài)作用域
})(foo);

我們看到,采用動(dòng)態(tài)作用域,變量(標(biāo)識(shí)符)的系統(tǒng)是通過變量動(dòng)態(tài)棧來管理的。因此,自由變量是在當(dāng)前活躍的動(dòng)態(tài)鏈中查詢的,而不是在函數(shù)創(chuàng)建的時(shí)候保存起來的靜態(tài)作用域鏈中查詢的。

這樣就會(huì)產(chǎn)生沖突。比方說,即使Z仍然存在(與之前從棧中移除變量的例子相反),還是會(huì)有這樣一個(gè)問題: 在不同的函數(shù)調(diào)用中,Z 的值到底取哪個(gè)呢(從哪個(gè)上下文,哪個(gè)作用域中查詢)?

上述描述的就是兩類 funarg 問題 —— 取決于是否將函數(shù)以返回值返回(第一類問題)以及是否將函數(shù)當(dāng)函數(shù)參數(shù)使用(第二類問題)。

為了解決上述問題,就引入了 閉包的概念。

閉包

閉包是代碼塊和創(chuàng)建該代碼塊的上下文中數(shù)據(jù)的結(jié)合。

讓我們來看下面這個(gè)例子(偽代碼):

var x = 20;
function foo() {
  alert(x); // 自由變量"x" == 20
}
// 為foo閉包
fooClosure = {
  call: foo // 引用到function
  lexicalEnvironment: {x: 20} // 搜索上下文的上下文
};

上述例子中,“fooClosure”部分是偽代碼。對應(yīng)的,在 ECMAScript 中,“foo”函數(shù)已經(jīng)有了一個(gè)內(nèi)部屬性——?jiǎng)?chuàng)建該函數(shù)上下文的作用域鏈。

“l(fā)exical”通常是省略的。上述例子中是為了強(qiáng)調(diào)在閉包創(chuàng)建的同時(shí),上下文的數(shù)據(jù)就會(huì)保存起來。當(dāng)下次調(diào)用該函數(shù)的時(shí)候,自由變量就可以在保存的(閉包)上下文中找到了,正如上述代碼所示,變量“z”的值總是 10。

定義中我們使用的比較廣義的詞 —— “代碼塊”,然而,通常(在 ECMAScript 中)會(huì)使用我們經(jīng)常用到的函數(shù)。當(dāng)然了,并不是所有對閉包的實(shí)現(xiàn)都會(huì)將閉包和函數(shù)綁在一起,比方說,在 Ruby 語言中,閉包就有可能是: 一個(gè)過程對象(procedure object),一個(gè) lambda 表達(dá)式或者是代碼塊。

對于要實(shí)現(xiàn)將局部變量在上下文銷毀后仍然保存下來,基于棧的實(shí)現(xiàn)顯然是不適用的(因?yàn)榕c基于棧的結(jié)構(gòu)相矛盾)。因此在這種情況下,上層作用域的閉包數(shù)據(jù)是通過 動(dòng)態(tài)分配內(nèi)存的方式來實(shí)現(xiàn)的(基于“堆”的實(shí)現(xiàn)),配合使用垃圾回收器(garbage collector 簡稱 GC)和 引用計(jì)數(shù)(reference counting)。這種實(shí)現(xiàn)方式比基于棧的實(shí)現(xiàn)性能要低,然而,任何一種實(shí)現(xiàn)總是可以優(yōu)化的: 可以分析函數(shù)是否使用了自由變量,函數(shù)式參數(shù)或者函數(shù)式值,然后根據(jù)情況來決定 —— 是將數(shù)據(jù)存放在堆棧中還是堆中。

ECMAScript閉包的實(shí)現(xiàn)

討論完理論部分,接下來讓我們來介紹下 ECMAScript 中閉包究竟是如何實(shí)現(xiàn)的。這里還是有必要再次強(qiáng)調(diào)下:ECMAScript 只使用靜態(tài)(詞法)作用域(而諸如 Perl 這樣的語言,既可以使用靜態(tài)作用域也可以使用動(dòng)態(tài)作用域進(jìn)行變量聲明)。

var x = 10;
function foo() {
  alert(x);
}
(function (funArg) {
  var x = 20;
  // 變量"x"在(lexical)上下文中靜態(tài)保存的,在該函數(shù)創(chuàng)建的時(shí)候就保存了
  funArg(); // 10, 而不是20
})(foo);

技術(shù)上說,創(chuàng)建該函數(shù)的父級(jí)上下文的數(shù)據(jù)是保存在函數(shù)的內(nèi)部屬性 [[Scope]]中的。如果你還不了解什么是[[Scope]],建議你先閱讀第 14 章, 該章節(jié)對[[Scope]]作了非常詳細(xì)的介紹。如果你對[[Scope]]和作用域鏈的知識(shí)完全理解了的話,那對閉包也就完全理解了。

根據(jù)函數(shù)創(chuàng)建的算法,我們看到在 ECMAScript 中,所有的函數(shù)都是閉包,因?yàn)樗鼈兌际窃趧?chuàng)建的時(shí)候就保存了上層上下文的作用域鏈(除開異常的情況) (不管這個(gè)函數(shù)后續(xù)是否會(huì)激活 —— [[Scope]]在函數(shù)創(chuàng)建的時(shí)候就有了):

var x = 10;
function foo() {
  alert(x);
}
// foo是閉包
foo: <FunctionObject> = {
  [[Call]]: <code block of foo>,
  [[Scope]]: [
    global: {
      x: 10
    }
  ],
  ... // 其它屬性
};

如我們所說,為了優(yōu)化目的,當(dāng)一個(gè)函數(shù)沒有使用自由變量的話,實(shí)現(xiàn)可能不保存在副作用域鏈里。不過,在 ECMA-262-3 規(guī)范里任何都沒說。因此,正常來說,所有的參數(shù)都是在創(chuàng)建階段保存在[[Scope]]屬性里的。

有些實(shí)現(xiàn)中,允許對閉包作用域直接進(jìn)行訪問。比如 Rhino,針對函數(shù)的[[Scope]]屬性,對應(yīng)有一個(gè)非標(biāo)準(zhǔn)的__parent__屬性,在第 12 章中作過介紹:

var global = this;
var x = 10;
var foo = (function () {
  var y = 20;
  return function () {
    alert(y);
  };
})();
foo(); // 20
alert(foo.__parent__.y); // 20
foo.__parent__.y = 30;
foo(); // 30
// 可以通過作用域鏈移動(dòng)到頂部
alert(foo.__parent__.__parent__ === global); // true
alert(foo.__parent__.__parent__.x); // 10

所有對象都引用一個(gè)[[Scope]]

這里還要注意的是:在 ECMAScript 中,同一個(gè)父上下文中創(chuàng)建的閉包是共用一個(gè)[[Scope]]屬性的。也就是說,某個(gè)閉包對其中[[Scope]]的變量做修改會(huì)影響到其他閉包對其變量的讀?。?/p>

這就是說:所有的內(nèi)部函數(shù)都共享同一個(gè)父作用域

var firstClosure;
var secondClosure;
function foo() {
  var x = 1;
  firstClosure = function () { return ++x; };
  secondClosure = function () { return --x; };
  x = 2; // 影響 AO["x"], 在2個(gè)閉包公有的[[Scope]]中
  alert(firstClosure()); // 3, 通過第一個(gè)閉包的[[Scope]]
}
foo();
alert(firstClosure()); // 4
alert(secondClosure()); // 3

關(guān)于這個(gè)功能有一個(gè)非常普遍的錯(cuò)誤認(rèn)識(shí),開發(fā)人員在循環(huán)語句里創(chuàng)建函數(shù)(內(nèi)部進(jìn)行計(jì)數(shù))的時(shí)候經(jīng)常得不到預(yù)期的結(jié)果,而期望是每個(gè)函數(shù)都有自己的值。

var data = [];
for (var k = 0; k < 3; k++) {
  data[k] = function () {
    alert(k);
  };
}
data[0](); // 3, 而不是0
data[1](); // 3, 而不是1
data[2](); // 3, 而不是2

上述例子就證明了 —— 同一個(gè)上下文中創(chuàng)建的閉包是共用一個(gè)[[Scope]]屬性的。因此上層上下文中的變量“k”是可以很容易就被改變的。

activeContext.Scope = [
  ... // 其它變量對象
  {data: [...], k: 3} // 活動(dòng)對象
];
data[0].[[Scope]] === Scope;
data[1].[[Scope]] === Scope;
data[2].[[Scope]] === Scope;

這樣一來,在函數(shù)激活的時(shí)候,最終使用到的k就已經(jīng)變成了 3 了。如下所示,創(chuàng)建一個(gè)閉包就可以解決這個(gè)問題了:

var data = [];
for (var k = 0; k < 3; k++) {
  data[k] = (function _helper(x) {
    return function () {
      alert(x);
    };
  })(k); // 傳入"k"值
}
// 現(xiàn)在結(jié)果是正確的了
data[0](); // 0
data[1](); // 1
data[2](); // 2

讓我們來看看上述代碼都發(fā)生了什么?函數(shù)“_helper”創(chuàng)建出來之后,通過傳入?yún)?shù)“k”激活。其返回值也是個(gè)函數(shù),該函數(shù)保存在對應(yīng)的數(shù)組元素中。這種技術(shù)產(chǎn)生了如下效果: 在函數(shù)激活時(shí),每次“_helper”都會(huì)創(chuàng)建一個(gè)新的變量對象,其中含有參數(shù)“x”,“x”的值就是傳遞進(jìn)來的“k”的值。這樣一來,返回的函數(shù)的[[Scope]]就成了如下所示:

data[0].[[Scope]] === [
  ... // 其它變量對象
  父級(jí)上下文中的活動(dòng)對象AO: {data: [...], k: 3},
  _helper上下文中的活動(dòng)對象AO: {x: 0}
];
data[1].[[Scope]] === [
  ... // 其它變量對象
  父級(jí)上下文中的活動(dòng)對象AO: {data: [...], k: 3},
  _helper上下文中的活動(dòng)對象AO: {x: 1}
];
data[2].[[Scope]] === [
  ... // 其它變量對象
  父級(jí)上下文中的活動(dòng)對象AO: {data: [...], k: 3},
  _helper上下文中的活動(dòng)對象AO: {x: 2}
];

我們看到,這時(shí)函數(shù)的[[Scope]]屬性就有了真正想要的值了,為了達(dá)到這樣的目的,我們不得不在[[Scope]]中創(chuàng)建額外的變量對象。要注意的是,在返回的函數(shù)中,如果要獲取“k”的值,那么該值還是會(huì)是 3。

順便提下,大量介紹 JavaScript 的文章都認(rèn)為只有額外創(chuàng)建的函數(shù)才是閉包,這種說法是錯(cuò)誤的。實(shí)踐得出,這種方式是最有效的,然而,從理論角度來說,在 ECMAScript 中所有的函數(shù)都是閉包。

然而,上述提到的方法并不是唯一的方法。通過其他方式也可以獲得正確的“k”的值,如下所示:

var data = [];
for (var k = 0; k < 3; k++) {
  (data[k] = function () {
    alert(arguments.callee.x);
  }).x = k; // 將k作為函數(shù)的一個(gè)屬性
}
// 結(jié)果也是對的
data[0](); // 0
data[1](); // 1
data[2](); // 2

Funarg 和 return

另外一個(gè)特性是從閉包中返回。在 ECMAScript 中,閉包中的返回語句會(huì)將控制流返回給調(diào)用上下文(調(diào)用者)。而在其他語言中,比如,Ruby,有很多中形式的閉包,相應(yīng)的處理閉包返回也都不同,下面幾種方式都是可能的:可能直接返回給調(diào)用者,或者在某些情況下——直接從上下文退出。

ECMAScript 標(biāo)準(zhǔn)的退出行為如下:

function getElement() {
  [1, 2, 3].forEach(function (element) {
    if (element % 2 == 0) {
      // 返回給函數(shù)"forEach"函數(shù)
      // 而不是返回給getElement函數(shù)
      alert('found: ' + element); // found: 2
      return element;
    }
  });
  return null;
}

然而,在 ECMAScript 中通過 try catch 可以實(shí)現(xiàn)如下效果:

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

理論版本

這里說明一下,開發(fā)人員經(jīng)常錯(cuò)誤將閉包簡化理解成從父上下文中返回內(nèi)部函數(shù),甚至理解成只有匿名函數(shù)才能是閉包。

再說一下,因?yàn)樽饔糜蜴?,使得所有的函?shù)都是閉包(與函數(shù)類型無關(guān): 匿名函數(shù),F(xiàn)E,NFE,F(xiàn)D 都是閉包)。 這里只有一類函數(shù)除外,那就是通過 Function 構(gòu)造器創(chuàng)建的函數(shù),因?yàn)槠鋄[Scope]]只包含全局對象。

為了更好的澄清該問題,我們對 ECMAScript 中的閉包給出 2 個(gè)正確的版本定義:

ECMAScript 中,閉包指的是:

  1. 從理論角度:所有的函數(shù)。因?yàn)樗鼈兌荚趧?chuàng)建的時(shí)候就將上層上下文的數(shù)據(jù)保存起來了。哪怕是簡單的全局變量也是如此,因?yàn)楹瘮?shù)中訪問全局變量就相當(dāng)于是在訪問自由變量,這個(gè)時(shí)候使用最外層的作用域。
  2. 從實(shí)踐角度:以下函數(shù)才算是閉包:

    1. 即使創(chuàng)建它的上下文已經(jīng)銷毀,它仍然存在(比如,內(nèi)部函數(shù)從父函數(shù)中返回)
    2. 在代碼中引用了自由變量

閉包用法實(shí)戰(zhàn)

實(shí)際使用的時(shí)候,閉包可以創(chuàng)建出非常優(yōu)雅的設(shè)計(jì),允許對 funarg 上定義的多種計(jì)算方式進(jìn)行定制。如下就是數(shù)組排序的例子,它接受一個(gè)排序條件函數(shù)作為參數(shù):

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

同樣的例子還有,數(shù)組的 map 方法是根據(jù)函數(shù)中定義的條件將原數(shù)組映射到一個(gè)新的數(shù)組中:

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

使用函數(shù)式參數(shù),可以很方便的實(shí)現(xiàn)一個(gè)搜索方法,并且可以支持無限制的搜索條件:

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

還有應(yīng)用函數(shù),比如常見的 forEach 方法,將函數(shù)應(yīng)用到每個(gè)數(shù)組元素:

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

順便提下,函數(shù)對象的 apply 和 call 方法,在函數(shù)式編程中也可以用作應(yīng)用函數(shù)。 apply 和 call 已經(jīng)在討論“this”的時(shí)候介紹過了;這里,我們將它們看作是應(yīng)用函數(shù) —— 應(yīng)用到參數(shù)中的函數(shù)(在 apply 中是參數(shù)列表,在 call 中是獨(dú)立的參數(shù)):

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

閉包還有另外一個(gè)非常重要的應(yīng)用 —— 延遲調(diào)用:

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

還有回調(diào)函數(shù)

//...
var x = 10;
// only for example
xmlHttpRequestObject.onreadystatechange = function () {
  // 當(dāng)數(shù)據(jù)就緒的時(shí)候,才會(huì)調(diào)用;
  // 這里,不論是在哪個(gè)上下文中創(chuàng)建
  // 此時(shí)變量“x”的值已經(jīng)存在了
  alert(x); // 10
};
//...

還可以創(chuàng)建封裝的作用域來隱藏輔助對象:

var foo = {};
// 初始化
(function (object) {
  var x = 10;
  object.getX = function _getX() {
    return x;
  };
})(foo);
alert(foo.getX()); // 獲得閉包 "x" – 10

總結(jié)

本文介紹了更多關(guān)于 ECMAScript-262-3 的理論知識(shí),而我認(rèn)為,這些基礎(chǔ)的理論有助于理解 ECMAScript 中閉包的概念。

其它參考

  1. Javascript Closures (by Richard Cornford)
  2. Funarg problem
  3. Closures