欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

從理論角度討論JavaScript閉包

 更新時(shí)間:2019年04月03日 16:10:17   作者:Skylor.min  
本文將介紹一個(gè)在JavaScript經(jīng)常會(huì)拿來(lái)討論的話(huà)題 —— 閉包(closure)。 閉包其實(shí)已經(jīng)是個(gè)老生常談的話(huà)題了; 有大量文章都介紹過(guò)閉包的內(nèi)容, 盡管如此,這里還是要試著從理論角度來(lái)討論下閉包, 看看ECMAScript中的閉包內(nèi)部究竟是如何工作的
概論

在討論ECMAScript閉包之前,先來(lái)介紹下函數(shù)式編程(與ECMA-262-3 標(biāo)準(zhǔn)無(wú)關(guān))中一些基本定義。 然而,為了更好的解釋這些定義,這里還是拿ECMAScript來(lái)舉例。

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

定義

函數(shù)式參數(shù)(“Funarg”) —— 是指值為函數(shù)的參數(shù)。

如下例子:

function exampleFunc(funArg) {
 funArg();
 }

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

上述例子中funArg的實(shí)參是一個(gè)傳遞給exampleFunc的匿名函數(shù)。

反過(guò)來(lái),接受函數(shù)式參數(shù)的函數(shù)稱(chēng)為 高階函數(shù)(high-order function 簡(jiǎn)稱(chēng):HOF)。還可以稱(chēng)作:函數(shù)式函數(shù) 或者 偏數(shù)理的叫法:操作符函數(shù)。 上述例子中,exampleFunc 就是這樣的函數(shù)。

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

(function functionValued() {
        return function () {
            alert('returned function is called');
            };
    })()();//這種()直接執(zhí)行的方式要熟悉。

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

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

(function selfApplicative(funArg) {

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

 selfApplicative(selfApplicative);

 })();

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

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

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

function testFn(funArg) {

 // 激活funArg, 本地變量localVar可訪(fǎng)問(wèn)
 funArg(10); // 20
 funArg(20); // 30

 }

 testFn(function (arg) {

 var localVar = 10;
 alert(arg + localVar);

 });

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

FunArg問(wèn)題

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

當(dāng)函數(shù)返回的時(shí)候,這些參數(shù)又會(huì)從堆棧中移除。這種模型對(duì)將函數(shù)作為函數(shù)式值使用的時(shí)候有很大的限制(比方說(shuō),作為返回值從父函數(shù)中返回)。 絕大部分情況下,問(wèn)題會(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

上述例子中,對(duì)于innerFn函數(shù)來(lái)說(shuō),localVar就屬于自由變量。

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

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

還有一個(gè)函數(shù)對(duì)象問(wèn)題和當(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)作用域情況下都是

 (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í)符)處理是通過(guò)動(dòng)態(tài)堆棧來(lái)管理的。 因此,自由變量是在當(dāng)前活躍的動(dòng)態(tài)鏈中查詢(xún)的,而不是在函數(shù)創(chuàng)建的時(shí)候保存起來(lái)的靜態(tài)作用域鏈中查詢(xún)的。

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

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

為了解決上述問(wèn)題,就引入了 閉包的概念。下面重點(diǎn)來(lái)了,閉包的面紗就此揭開(kāi)。

閉包

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

讓我們來(lái)看下面這個(gè)例子:

var x = 20;

 function foo() {
 alert(x); // 自由變量 "x" == 20
 }

 // foo的閉包
 fooClosure = {
 call: foo // 對(duì)函數(shù)的引用
 lexicalEnvironment: {x: 20} // 查詢(xún)自由變量的上下文
 };

上述例子中,“fooClosure”部分是偽代碼。對(duì)應(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ì)保存起來(lái)。 當(dāng)下次調(diào)用該函數(shù)的時(shí)候,自由變量就可以在保存的(閉包)上下文中找到了,正如上述代碼所示,變量“z”的值總是10。

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

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

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

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

var x = 10;

 function foo() {
 alert(x);
 }

 (function (funArg) {

 var x = 20;

 // funArg的變量 "x" 是靜態(tài)保存的,在該函數(shù)創(chuàng)建的時(shí)候就保存了

 funArg(); // 10, 而不是 20

 })(foo);

從技術(shù)角度來(lái)說(shuō),創(chuàng)建該函數(shù)的上層上下文的數(shù)據(jù)是保存在函數(shù)的內(nèi)部屬性[[Scope]]中的。如果你對(duì)[[Scope]]和作用域鏈的知識(shí)完全理解了的話(huà),那對(duì)閉包也就完全理解了。

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

var x = 10;

 function foo() {
 alert(x);
 }

 // foo is a closure
 foo: FunctionObject = {
 [[Call]]: code block of foo,
 [[Scope]]: [
 global: {
 x: 10
 }
 ],
 ... // other properties
 };

正如此前提到過(guò)的,出于優(yōu)化的目的,當(dāng)函數(shù)不使用自由變量的時(shí)候,實(shí)現(xiàn)層可能就不會(huì)保存上層作用域鏈。 然而,ECMAScript-262-3標(biāo)準(zhǔn)中并未對(duì)此作任何說(shuō)明;因此,嚴(yán)格來(lái)說(shuō) —— 所有函數(shù)都會(huì)在創(chuàng)建的時(shí)候?qū)⑸蠈幼饔糜蜴湵4嬖赱[Scope]]中。

“萬(wàn)能”的[[Scope]]

這里不得不說(shuō)JavaScript的作用域鏈

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

var firstClosure;
 var secondClosure;

 function foo() {

 var x = 1;

 firstClosure = function () { return ++x; };
 secondClosure = function () { return --x; };

 x = 2; // 對(duì)AO["x"]產(chǎn)生了影響, 其值在兩個(gè)閉包的[[Scope]]中

 alert(firstClosure()); // 3, 通過(guò) firstClosure.[[Scope]]
 }

 foo();

 alert(firstClosure()); // 4
 alert(secondClosure()); // 3

正因?yàn)檫@個(gè)特性,很多人都會(huì)犯一個(gè)非常常見(jiàn)的錯(cuò)誤: 當(dāng)在循環(huán)中創(chuàng)建了函數(shù),然后將循環(huán)的索引值和每個(gè)函數(shù)綁定的時(shí)候,通常得到的結(jié)果不是預(yù)期的(預(yù)期是希望每個(gè)函數(shù)都能夠獲取各自對(duì)應(yīng)的索引值)。這個(gè)錯(cuò)誤也是題目中說(shuō)提到的那段代碼的最大錯(cuò)誤的地方,下面我們來(lái)揭秘為啥button點(diǎn)擊彈出來(lái)的都是5.

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 = [
 ... // higher variable objects
 {data: [...], k: 3} // activation object
 ];

 data[0].[[Scope]] === Scope;
 data[1].[[Scope]] === Scope;
 data[2].[[Scope]] === Scope;

這樣一來(lái),在函數(shù)激活的時(shí)候,最終使用到的k就已經(jīng)變成了3了。

如下所示,創(chuàng)建一個(gè)額外的閉包就可以解決這個(gè)問(wèn)題了:

var data = [];

 for (var k = 0; k < 3; k++) {
 data[k] = (function _helper(x) {
 return function () {
 alert(x);
 };
 })(k); // 將 "k" 值傳遞進(jìn)去
 }

 // 現(xiàn)在就對(duì)了
 data[0](); // 0
 data[1](); // 1
 data[2](); // 2

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

data[0].[[Scope]] === [
 ... // 更上層的變量對(duì)象
 上層上下文的AO: {data: [...], k: 3},
 _helper上下文的AO: {x: 0}
 ];

 data[1].[[Scope]] === [
 ... // 更上層的變量對(duì)象
 上層上下文的AO: {data: [...], k: 3},
 _helper上下文的AO: {x: 1}
 ];

 data[2].[[Scope]] === [
 ... // 更上層的變量對(duì)象
 上層上下文的AO: {data: [...], k: 3},
 _helper上下文的AO: {x: 2}
 ];

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

順便提下,大量介紹JavaScript的文章都認(rèn)為只有額外創(chuàng)建的函數(shù)才是閉包,這種說(shuō)法是錯(cuò)誤的(我也差點(diǎn)有這個(gè)錯(cuò)誤的認(rèn)識(shí),再次感謝作者指出。)。 實(shí)踐得出,這種方式是最有效的,然而,從理論角度來(lái)說(shuō),在ECMAScript中所有的函數(shù)都是閉包。

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

var data = [];

 for (var k = 0; k < 3; k++) {
 (data[k] = function () {
 alert(arguments.callee.x);
 }).x = k; // 將“k”存儲(chǔ)為函數(shù)的一個(gè)屬性
 }

 // 同樣也是可行的
 data[0](); // 0
 data[1](); // 1
 data[2](); // 2

然而,arguments.callee從ECMAScript (ES5)中移除了,所以這個(gè)方法了解下就行了。

FunArg和return

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

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

function getElement() {

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

 if (element % 2 == 0) {
 // 返回給函數(shù)"forEach",
 // 而不會(huì)從getElement函數(shù)返回
 alert('found: ' + element); // found: 2
 return element;
 }

 });

 return null;
 }

 alert(getElement()); // null, 而不是 2

 然而,在ECMAScript中通過(guò)try catch可以實(shí)現(xiàn)如下效果(這個(gè)方法簡(jiǎ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
 理論版本

通常,程序員會(huì)錯(cuò)誤的認(rèn)為,只有匿名函數(shù)才是閉包。其實(shí)并非如此,正如我們所看到的 —— 正是因?yàn)樽饔糜蜴?,使得所有的函?shù)都是閉包(與函數(shù)類(lèi)型無(wú)關(guān): 匿名函數(shù),F(xiàn)E,NFE,F(xiàn)D都是閉包), 這里只有一類(lèi)函數(shù)除外,那就是通過(guò)Function構(gòu)造器創(chuàng)建的函數(shù),因?yàn)槠鋄[Scope]]只包含全局對(duì)象。 為了更好的澄清該問(wèn)題,我們對(duì)ECMAScript中的閉包作兩個(gè)定義(即兩種閉包):

ECMAScript中,閉包指的是:

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

                ♦即使創(chuàng)建它的上下文已經(jīng)銷(xiāo)毀,它仍然存在(比如,內(nèi)部函數(shù)從父函數(shù)中返回)

                ♦在代碼中引用了自由變量

閉包實(shí)踐

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

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

同樣的例子還有,數(shù)組的map方法(并非所有的實(shí)現(xiàn)都支持?jǐn)?shù)組map方法,SpiderMonkey從1.6版本開(kāi)始有支持),該方法根據(jù)函數(shù)中定義的條件將原數(shù)組映射到一個(gè)新的數(shù)組中:  

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

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

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

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

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

 順便提下,函數(shù)對(duì)象的 apply 和 call方法,在函數(shù)式編程中也可以用作應(yīng)用函數(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, 一秒鐘后
    }, 1000);

也可以用于回調(diào)函數(shù):

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

還可以用于封裝作用域來(lái)隱藏輔助對(duì)象:

var foo = {};

 // initialization
 (function (object) {

 var x = 10;

 object.getX = function _getX() {
 return x;
 };

 })(foo);

 alert(foo.getX()); // get closured "x" – 10
幾個(gè)閉包樣例

例子1:閉包中局部變量是引用而非拷貝

function say667() {
 // Local variable that ends up within closure
 var num = 666;
 var sayAlert = function() {
 alert(num);
 }
 num++;
 return sayAlert;
 }

 var sayAlert = say667();
 sayAlert()

因此執(zhí)行結(jié)果應(yīng)該彈出的667而非666。

例子2:多個(gè)函數(shù)綁定同一個(gè)閉包,因?yàn)樗麄兌x在同一個(gè)函數(shù)內(nèi)。

function setupSomeGlobals() {
 // Local variable that ends up within closure
 var num = 666;
 // Store some references to functions as global variables
 gAlertNumber = function() { alert(num); }
 gIncreaseNumber = function() { num++; }
 gSetNumber = function(x) { num = x; }
 }
 setupSomeGlobals(); // 為三個(gè)全局變量賦值
 gAlertNumber(); //666
 gIncreaseNumber();
 gAlertNumber(); // 667
 gSetNumber(12); //
 gAlertNumber(); //12

 例子3:當(dāng)在一個(gè)循環(huán)中賦值函數(shù)時(shí),這些函數(shù)將綁定同樣的閉包

function buildList(list) {
 var result = [];
 for (var i = 0; i < list.length; i++) {
 var item = 'item' + list[i];
 result.push( function() {alert(item + ' ' + list[i])} );
 }
 return result;
 }

 function testList() {
 var fnList = buildList([1,2,3]);
 // using j only to help prevent confusion - could use i
 for (var j = 0; j < fnList.length; j++) {
 fnList[j]();
 }
 }

 testList的執(zhí)行結(jié)果是彈出item3 undefined窗口三次,因?yàn)檫@三個(gè)函數(shù)綁定了同一個(gè)閉包,而且item的值為最后計(jì)算的結(jié)果,但是當(dāng)i跳出循環(huán)時(shí)i值為4,所以list[4]的結(jié)果為undefined.(再具體原因,前面有解釋過(guò)。)

例子4:外部函數(shù)所有局部變量都在閉包內(nèi),即使這個(gè)變量聲明在內(nèi)部函數(shù)定義之后。

function sayAlice() {
 var sayAlert = function() { alert(alice); }
 // Local variable that ends up within closure
 var alice = 'Hello Alice';
 return sayAlert;
 }
 var helloAlice=sayAlice();
 helloAlice();

執(zhí)行結(jié)果是彈出”Hello Alice”的窗口。即使局部變量聲明在函數(shù)sayAlert之后,局部變量仍然可以被訪(fǎng)問(wèn)到。

例子5:每次函數(shù)調(diào)用的時(shí)候創(chuàng)建一個(gè)新的閉包

function newClosure(someNum, someRef) {
 // Local variables that end up within closure
 var num = someNum;
 var anArray = [1,2,3];
 var ref = someRef;
 return function(x) {
 num += x;
 anArray.push(num);
 alert('num: ' + num +
 '\nanArray ' + anArray.toString() +
 '\nref.someVar ' + ref.someVar);
 }
 }
 closure1=newClosure(40,{someVar:'closure 1'});
 closure2=newClosure(1000,{someVar:'closure 2'});

 closure1(5); // num:45 anArray[1,2,3,45] ref:'someVar closure1'
 closure2(-10);// num:990 anArray[1,2,3,990] ref:'someVar closure2'

 Singleton 單件:

var singleton = function () {
 var privateVariable;
 function privateFunction(x) {
 ...privateVariable...
 }

 return {
 firstMethod: function (a, b) {
 ...privateVariable...
 },
 secondMethod: function (c) {
 ...privateFunction()...
 }
 };

 這個(gè)單件通過(guò)閉包來(lái)實(shí)現(xiàn)。通過(guò)閉包完成了私有的成員和方法的封裝。匿名主函數(shù)返回一個(gè)對(duì)象。對(duì)象包含了兩個(gè)方法,方法1可以方法私有變量,方法2訪(fǎng) 問(wèn)內(nèi)部私有函數(shù)。需要注意的地方是匿名主函數(shù)結(jié)束的地方的'()',如果沒(méi)有這個(gè)'()'就不能產(chǎn)生單件。因?yàn)槟涿瘮?shù)只能返回了唯一的對(duì)象,而且不能被 其他地方調(diào)用。這個(gè)就是利用閉包產(chǎn)生單件的方法。

以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,謝謝大家對(duì)腳本之家的支持。

相關(guān)文章

  • 小程序開(kāi)發(fā)?page-container?頁(yè)面容器彈出對(duì)話(huà)框功能的實(shí)現(xiàn)

    小程序開(kāi)發(fā)?page-container?頁(yè)面容器彈出對(duì)話(huà)框功能的實(shí)現(xiàn)

    這篇文章主要介紹了小程序開(kāi)發(fā)?page-container?頁(yè)面容器,彈出對(duì)話(huà)框,本文通過(guò)示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-08-08
  • js Canvas繪制圓形時(shí)鐘教程

    js Canvas繪制圓形時(shí)鐘教程

    這篇文章主要為大家詳細(xì)介紹了js Canvas繪制圓形時(shí)鐘教程,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-02-02
  • 關(guān)于__defineGetter__ 和__defineSetter__的說(shuō)明

    關(guān)于__defineGetter__ 和__defineSetter__的說(shuō)明

    關(guān)于__defineGetter__ 和__defineSetter__的說(shuō)明...
    2007-05-05
  • js獲取域名的方法

    js獲取域名的方法

    這篇文章主要介紹了js獲取域名的方法,涉及window.location中常見(jiàn)方法的使用技巧,需要的朋友可以參考下
    2015-01-01
  • JS DOM實(shí)現(xiàn)鼠標(biāo)滑動(dòng)圖片效果

    JS DOM實(shí)現(xiàn)鼠標(biāo)滑動(dòng)圖片效果

    這篇文章主要為大家詳細(xì)介紹了JS DOM實(shí)現(xiàn)鼠標(biāo)滑動(dòng)圖片效果,只要將鼠標(biāo)放上該商品的區(qū)域,原本折疊起來(lái)的商品便會(huì)自動(dòng)展開(kāi),感興趣的小伙伴們可以參考一下
    2016-03-03
  • 解讀input標(biāo)簽的value屬性及name屬性

    解讀input標(biāo)簽的value屬性及name屬性

    這篇文章主要介紹了解讀input標(biāo)簽的value屬性,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-01-01
  • JS新包管理工具yarn和npm的對(duì)比與使用入門(mén)

    JS新包管理工具yarn和npm的對(duì)比與使用入門(mén)

    在2016年10月11日f(shuō)acebook 公開(kāi)了新的javascript包管理工具 yarn, 用來(lái)替代目前被廣泛使用的npm(nodejs 自帶的包管理工具),本文將介紹yarn工具帶來(lái)的優(yōu)點(diǎn)和使用入門(mén),以及對(duì)比npm解決了什么問(wèn)題,帶來(lái)哪些便利。有需要的朋友們下面來(lái)一起看看吧。
    2016-12-12
  • javascript實(shí)現(xiàn)base64 md5 sha1 密碼加密

    javascript實(shí)現(xiàn)base64 md5 sha1 密碼加密

    本篇文章給大家介紹了javascript實(shí)現(xiàn)密碼加密,通過(guò)base64、md5、sha1文件,調(diào)用相關(guān)方法實(shí)現(xiàn)密碼加密,非常簡(jiǎn)單,需要的朋友可以參考下
    2015-09-09
  • Javascript拖拽系列文章1之offsetParent屬性

    Javascript拖拽系列文章1之offsetParent屬性

    這個(gè)系列文章主要是講述實(shí)現(xiàn)Javascript拖拽功能的基礎(chǔ)知識(shí),并將在最后給出一個(gè)完整的示例。適合對(duì)拖拽完全不懂的人閱讀
    2008-09-09
  • 利用原生JS實(shí)現(xiàn)歡樂(lè)水果機(jī)小游戲

    利用原生JS實(shí)現(xiàn)歡樂(lè)水果機(jī)小游戲

    這篇文章主要介紹了利用原生JS實(shí)現(xiàn)歡樂(lè)水果機(jī)小游戲,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-04-04

最新評(píng)論