色yeye在线视频观看_亚洲人亚洲精品成人网站_一级毛片免费播放_91精品一区二区中文字幕_一区二区三区日本视频_成人性生交大免费看

當前位置:首頁 > 嵌入式培訓(xùn) > 嵌入式學(xué)習(xí) > 講師博文 > javascript閉包闡述

javascript閉包闡述 時間:2018-09-28      來源:未知

前言:

眾所周知,javascript中的閉包很重要,但真正理解其語法含義,還存在很多不足,所以在此從各個方面講解Javascript中閉包涉及的一些含義:主要包括作用域機制、閉包等待

作用域在JavaScript程序員日常使用中有不同的含義,如下所示:

• this綁定的值;

• this綁定的值定義的執(zhí)行上下文;

• 一個變量的“生命周期”;

• 變量的值解析方案,或詞法綁定。

下面將講訴JavaScript作用域概念,由此引出變量值解析方案的一般想法,后再探討JavaScript里閉包這一重要知識點。

1.全局作用域

所有瀏覽器都支持 window 對象,它表示瀏覽器窗口,JavaScript 全局對象、函數(shù)以及變量均自動成為 window 對象的成員。所以,全局變量是 window 對象的屬性,全局函數(shù)是 window 對象的方法,甚至 HTML DOM 的 document 也是 window 對象的屬性之一。

全局變量是JavaScript里生命周期(一個變量多長時間內(nèi)保持一定的值)長的變量,其將跨越整個程序,可以被程序中的任何函數(shù)方法訪問。

在全局下聲明的變量都會在window對象下,都在全局作用域中,我們可以通過window對象訪問,也可以直接訪問。

1 var name = "jeri";

2 console.log(window.name); // 輸出:jeri

3 console.log(name); // 輸出:jeri

在JS中任何位置,沒有使用var關(guān)鍵字聲明的變量也都是全局變量。

1 function fun() {

2 name = "jeri";

3 alert(name);

4 }

5

6 console.log(name); // 輸出:jeri

全局變量存在于整個函數(shù)的生命周期中,然而其在全局范圍內(nèi)很容易被篡改,我們在使用全局變量時一定要小心,盡量不要使用全局變量。在函數(shù)內(nèi)部聲明變量沒有使用var也會產(chǎn)生全局變量,會為我們造成一些混亂,比如變量覆蓋等。所以,我們在聲明變量的任何時候好都要帶上var。

全局變量存在于程序的整個生命周期,但并不是通過其引用我們一定可以訪問到全局變量。

2.詞法作用域

詞法作用域:函數(shù)在定義它們的作用域里運行,而不是在執(zhí)行它們的作用域里運行。也就是說詞法作用域取決于源碼,通過靜態(tài)分析就能確定,因此詞法作用域也叫做靜態(tài)作用域。with和eval除外,所以只能說JS的作用域機制非常接近詞法作用域(Lexical scope)。詞法作用域也可以理解為一個變量的可見性,及其文本表述的模擬值。

1 var name = "global";

2

3 function fun() {

4 var name = "jeri";

5 return name;

6 }

7

8 console.log(fun()); // 輸出:jeri

9 console.log(name); // 輸出:global

在通常情況下,變量的查詢從近接的綁定上下文開始,向外部逐漸擴展,直到查詢到第一個綁定,一旦完成查找就結(jié)束搜索。就像上例,先查找離它近的name="jeri",查詢完成后就結(jié)束了,將第一個獲取的值作為變量的值。

3.動態(tài)作用域

在編程實踐中,容易低估和過度濫用的概念就是動態(tài)作用域,因為很少有語言支持這種方式為綁定解析方案。

動態(tài)作用域與詞法作用域相對而言的,不同于詞法作用域在定義時確定,動態(tài)作用域在執(zhí)行時確定,其生存周期到代碼片段執(zhí)行為止。動態(tài)變量存在于動態(tài)作用域中,任何給定的綁定的值,在確定調(diào)用其函數(shù)之前,都是不可知的。

在代碼執(zhí)行時,對應(yīng)的作用域鏈常常是保持靜態(tài)的。然而當遇到with語句、call方法、apply方法和try-catch中的catch時,會改變作用域鏈的。以with為例,在遇到with語句時,會將傳入的對象屬性作為局部變量來顯示,使其便于訪問,也就是說把一個新的對象添加到了作用域鏈的頂端,這樣必然影響對局部標志符的解析。當with語句執(zhí)行完畢后,會把作用域鏈恢復(fù)到原始狀態(tài)。實例如下:

1 var name = "global";

2

3 // 使用with之前

4 console.log(name); // 輸出:global

5

6 with({name:"jeri"}){

7 console.log(name); // 輸出:jeri

8 }

9

10 // 使用with之后,作用域鏈恢復(fù)

11 console.log(name); // 輸出:global

在作用域鏈中有動態(tài)作用域時,this引用也會變得更加復(fù)雜,不再指向第一次創(chuàng)建時的上下文,而是由調(diào)用者確定。比如在使用apply或call方法時,傳入它們的第一個參數(shù)就是被引用的對象。實例如下:

1 function globalThis() {

2 console.log(this);

3 }

4

5 globalThis(); // 輸出:Window {document: document,external: Object…}

6 globalThis.call({name:"jeri"}); // 輸出:Object {name: "jeri"}

7 globalThis.apply({name:"jeri"},[]); // 輸出:Object {name: "jeri"}

因為this引用是動態(tài)作用域,所以在編程過程中一定要注意this引用的變化,及時跟蹤this的變動。

4.函數(shù)作用域

函數(shù)作用域,顧名思義就是在定義函數(shù)時候產(chǎn)生的作用域,這個作用域也可以稱為局部作用域。和全局作用域相反,函數(shù)作用域一般只在函數(shù)的代碼片段內(nèi)可訪問到,外部不能進行變量訪問。在函數(shù)內(nèi)部定義的變量存在于函數(shù)作用域中,其生命周期隨著函數(shù)的執(zhí)行結(jié)束而結(jié)束。實例如下:

1 var name = "global";

2

3 function fun() {

4 var name = "jeri";

5 console.log(name); // 輸出:jeri

6

7 with ({name:"with"}) {

8 console.log(name); // 輸出:with

9 }

10 console.log(name); // 輸出:jeri

11 }

12

13 fun();

14

15 // 不能訪問函數(shù)作用域

16 console.log(name); // 輸出:global

5.沒有塊級作用域

不同于其他編程語言,在JavaScript里并沒有塊級作用域,也就是說在for、if、while等語句內(nèi)部的聲明的變量與在外部聲明是一樣的,在這些語句外部也可以訪問和修改這些變量的值。實例如下:

1 function fun() {

2

3 if(0 < 2) {

4 var name = "jeri";

5 }

6 console.log(name); // 輸出:jeri

7 name = "change";

8 console.log(name); // 輸出:change

9 }

10

11 fun();

6.作用域鏈

JavaScript里一切皆為對象,包括函數(shù)。函數(shù)對象和其它對象一樣,擁有可以通過代碼訪問的屬性和一系列僅供JavaScript引擎訪問的內(nèi)部屬性。其中一個內(nèi)部屬性是作用域,包含了函數(shù)被創(chuàng)建的作用域中對象的集合,稱為函數(shù)的作用域鏈,它用來保證對執(zhí)行環(huán)境有權(quán)訪問的變量和函數(shù)的有序訪問。

當一個函數(shù)創(chuàng)建后,它的作用域鏈會被創(chuàng)建此函數(shù)的作用域中可訪問的數(shù)據(jù)對象填充。在全局作用域中創(chuàng)建的函數(shù),其作用域鏈會自動成為全局作用域中的一員。而當函數(shù)執(zhí)行時,其活動對象就會成為作用域鏈中的第一個對象(活動對象:對象包含了函數(shù)的所有局部變量、命名參數(shù)、參數(shù)集合以及this)。在程序執(zhí)行時,Javascript引擎會通過搜索上下文的作用域鏈來解析諸如變量和函數(shù)名這樣的標識符。其會從作用域鏈的里面開始檢索,按照由內(nèi)到外的順序,直到完成查找,一旦完成查找就結(jié)束搜索。如果沒有查詢到標識符聲明,則報錯。當函數(shù)執(zhí)行結(jié)束,運行期上下文被銷毀,活動對象也隨之銷毀。實例如下:

1 var name = 'global';

2

3 function fun() {

4 console.log(name); // output:global

5 name = "change";

6 // 函數(shù)內(nèi)部可以修改全局變量

7 console.log(name); // output:change

8 // 先查詢活動對象

9 var age = "18";

10 console.log(age); // output:18

11 }

12

13 fun();

14

15 // 函數(shù)執(zhí)行完畢,執(zhí)行環(huán)境銷毀

16 console.log(age); // output:Uncaught ReferenceError: age is not defined

7.閉包

閉包是JavaScript的一大謎團,關(guān)于這個問題有很多文章進行講述,然而依然有相當數(shù)量的程序員對這個概念理解不透徹。閉包的官方定義為:一個擁有許多變量和綁定了這些變量的環(huán)境的表達式(通常是一個函數(shù)),因而這些變量也是該表達式的一部分。

一句話概括就是:閉包就是一個函數(shù),捕獲作用域內(nèi)的外部綁定。這些綁定是為之后使用而被綁定,即使作用域已經(jīng)銷毀。

自由變量

自由變量與閉包的關(guān)系是,自由變量閉合于閉包的創(chuàng)建。閉包背后的邏輯是,如果一個函數(shù)內(nèi)部有其他函數(shù),那么這些內(nèi)部函數(shù)可以訪問在這個外部函數(shù)中聲明的變量(這些變量就稱之為自由變量)。然而,這些變量可以被內(nèi)部函數(shù)捕獲,從高階函數(shù)(返回另一個函數(shù)的函數(shù)稱為高階函數(shù))中return語句實現(xiàn)“越獄”,以供以后使用。內(nèi)部函數(shù)在沒有任何局部聲明之前(既不是被傳入,也不是局部聲明)使用的變量就是被捕獲的變量。實例如下:

1 function makeAdder(captured) {

2 return function(free) {

3 var ret = free + captured;

4 console.log(ret);

5 }

6 }

7

8 var add10 = makeAdder(10);

9

10 add10(2); // 輸出:12

從上例可知,外部函數(shù)中的變量captured被執(zhí)行加法的返回函數(shù)捕獲,內(nèi)部函數(shù)從未聲明過captured變量,卻可以引用它。

如果我們再創(chuàng)建一個加法器將捕獲到同名變量captured,但有不同的值,因為這個加法器是在調(diào)用makeAdder之后被創(chuàng)建:

1 var add16 = makeAdder(16);

2

3 add16(18); // 輸出:24

4

5 add10(10); // 輸出:20

如上述代碼所示,每一個新的加法器函數(shù)都保留了自己創(chuàng)建時捕獲的captured實例。

變量遮蔽

在JavaScript中,當變量在一定作用域內(nèi)聲明,然后在另一個同名變量在一個較低的作用域聲明,會發(fā)生變量的遮蔽。實例如下:

1 var name = "jeri";

2 var name = "tom";

3

4 function glbShadow() {

5 var name = "fun";

6

7 console.log(name); // 輸出:fun

8 }

9

10 glbShadow();

11

12 console.log(name); // 輸出:tom

當在一個變量同一作用域內(nèi)聲明了多次時,后一次聲明會生效,會遮蔽以前的聲明。

變量聲明的遮蔽很好理解,然而函數(shù)參數(shù)的遮蔽就略顯復(fù)雜。例如:

1 var shadowed = 0;

2

3 function argShadow(shadowed) {

4 var str = ["Value is",shadowed].join(" ");

5 console.log(str);

6 }

7

8 argShadow(108); // output:Value is 108

9

10 argShadow(); // output:Value is

函數(shù)argShadow的參數(shù)shadowed覆蓋了全局作用域內(nèi)的同名變量。即使沒有傳遞任何參數(shù),仍然綁定的是shadowed,并沒有訪問到全局變量shadowed = 0。

任何情況下,離得近的變量綁定優(yōu)先級高。實例如下:

1 var shadowed = 0;

2

3 function varShadow(shadowed) {

4 var shadowed = 123;

5 var str = ["Value is",shadowed].join(" ");

6 console.log(str);

7 }

8

9 varShadow(108); // output:Value is 123

10

11 varShadow(); // output:Value is 123

varShadow(108)打印出來的并不是108而是123,即使沒有參數(shù)傳入也是打印的123,先訪問離得近的變量綁定。

遮蔽變量同樣發(fā)生在閉包內(nèi)部,實例如下:

1 function captureShadow(shadowed) {

2

3 console.log(shadowed); // output:108

4

5 return function(shadowed) {

6

7 console.log(shadowed); // output:2

8 var ret = shadowed + 1;

9 console.log(ret); // output:3

10 }

11 }

12

13 var closureShadow = captureShadow(108);

14

15 closureShadow(2);

在編寫JavaScript代碼時,因為變量遮蔽會使很多變量綁定超出我們的控制,我們應(yīng)盡量避免變量遮蔽,一定要注意變量命名。

典型誤區(qū)

下面是一個非常典型的問題,曾經(jīng)困擾了很多人,下面也來探討下。

1 var test = function() {

2 var ret = [];

3

4 for(var i = 0; i < 5; i++) {

5 ret[i] = function() {

6 return i;

7 }

8 }

9

10 return ret;

11 };

12 var test0 = test()[0]();

13 console.log(test0); // 輸出:5

14

15 var test1 = test()[1]();

16 console.log(test1); //輸出:5

從上面的例子可知,test這個函數(shù)執(zhí)行之后返回一個函數(shù)數(shù)組,表面上看數(shù)組內(nèi)的每個函數(shù)都應(yīng)該返回自己的索引值,然而并不是如此。當外部函數(shù)執(zhí)行完畢后,外部函數(shù)雖然其執(zhí)行環(huán)境已經(jīng)銷毀,但閉包依然保留著對其中變量綁定的引用,仍然駐留在內(nèi)存之中。當外部函數(shù)執(zhí)行完畢之后,才會執(zhí)行內(nèi)部函數(shù),而這時內(nèi)部函數(shù)捕獲的變量綁定已經(jīng)是外部函數(shù)執(zhí)行之后的終變量值了,所以這些函數(shù)都引用的是同一個變量i=5。

下面有個更優(yōu)雅的例子來表述這個問題:

1 for(var i = 0; i < 5; i++) {

2

3 setTimeout(function() {

4 console.log(i);

5 }, 1000);

6 }

7

8 // 每隔1秒輸出一個5

按照我們的推斷,上例應(yīng)該輸出1,2,3,4,5。然而,事實上輸出的是連續(xù)5個5。為什么出現(xiàn)這種詭異的狀況呢?其本質(zhì)上還是由閉包特性造成的,閉包可以捕獲外部作用域的變量綁定。

上面這個函數(shù)片段在執(zhí)行時,其內(nèi)部函數(shù)和外部函數(shù)并不是同步執(zhí)行的,因為當調(diào)用setTimeout時會有一個延時事件排入隊列,等所有同步代碼執(zhí)行完畢后,再依次執(zhí)行隊列中的延時事件,而這個時候 i 已經(jīng) 是5了。

那怎么解決這個問題呢?我們是不是可以在每個循環(huán)執(zhí)行時,給內(nèi)部函數(shù)傳進一個變量的拷貝,使其在每次創(chuàng)建閉包時,都捕獲一個變量綁定。因為我們每次傳參不同,那么每次捕獲的變量綁定也是不同的,也就避免了后輸出5個5的狀況。實例如下:

1 for(var i = 0; i < 5; i++) {

2

3 (function(j) {

4

5 setTimeout(function() {

6 console.log(j);

7 }, 1000);

8 })(i);

9 }

10

11 // 輸出:0,1,2,3,4

閉包具有非常強大的功能,函數(shù)內(nèi)部可以引用外部的參數(shù)和變量,但其參數(shù)和變量不會被垃圾回收機制回,常駐內(nèi)存,會增大內(nèi)存使用量,使用不當很容易造成內(nèi)存泄露。但,閉包也是javascript語言的一大特點,主要應(yīng)用閉包場合為:設(shè)計私有的方法和變量。

模擬私有變量

從上文的敘述我們知道,變量的捕獲發(fā)生在創(chuàng)建閉包的時候,那么我們可以把閉包捕獲到的變量作為私有變量。實例如下:

1 var closureDemo = (function() {

2 var PRIVATE = 0;

3

4 return {

5 inc:function(n) {

6 return PRIVATE += n;

7 },

8 dec:function(n) {

9 return PRIVATE -= n;

10 }

11 };

12 })();

13

14 var testInc = closureDemo.inc(10);

15 //console.log(testInc);

16 // 輸出:10

17

18 var testDec = closureDemo.dec(7);

19 //console.log(testDec);

20 // 輸出:3

21

22 closureDemo.div = function(n) {

23 return PRIVATE / n;

24 };

25

26 var testDiv = closureDemo.div(3);

27 console.log(testDiv);

28 //輸出:Uncaught ReferenceError: PRIVATE is not defined

自執(zhí)行函數(shù)closureDemo執(zhí)行完畢之后,自執(zhí)行函數(shù)作用域和PRIVATE隨之銷毀,但PRIVATE仍滯留在內(nèi)存中,也就是加入到closureDemo.inc和closureDemo.dec的作用域鏈中,閉包也就完成了變量的捕獲。但之后新加入的closureDemo.div并不能在作用域中繼續(xù)尋找到PRIVATE了。因為,函數(shù)只有被調(diào)用時才會執(zhí)行函數(shù)里面的代碼,變量的捕獲也只發(fā)生在創(chuàng)建閉包時,所以之后新加入的div方法并不能捕獲PRIVATE。

創(chuàng)建特權(quán)方法

通過閉包我們可以創(chuàng)建私有作用域,那么也就可以創(chuàng)建私有變量和私有函數(shù)。創(chuàng)建私有函數(shù)的方式和聲明私有變量方法一致,只要在函數(shù)內(nèi)部聲明函數(shù)就可以了。當然,既然可以模擬私有變量和私有函數(shù),我們也可以利用閉包這個特性,創(chuàng)建特權(quán)方法。實例如下:

1 (function() {

2

3 // 私有變量和私有函數(shù)

4 var privateVar = 10;

5

6 function privateFun() {

7 return false;

8 };

9

10 // 構(gòu)造函數(shù)

11 MyObj = function() {

12

13 };

14

15 // 公有/特權(quán)方法

16 MyObj.prototype.publicMethod = function() {

17 privateVar ++;

18 return privateFun();

19 }

20 })();

上面這個實例創(chuàng)建了一個私有作用域,并封裝了一個構(gòu)造函數(shù)和對應(yīng)的方法。需要注意的是在上面的實例中,在聲明MyObj這個函數(shù)時,使用的是不帶var的函數(shù)表達式,我們希望產(chǎn)生的是一個全局函數(shù)而不是局部的,不然我們依然在外部無法訪問。所以,MyObj就成為了一個全局變量,能夠在外部進行訪問,我們在原型上定義的方法publicMethod也就可以使用,通過這個方法我們也就可以訪問私有函數(shù)和私有變量了。

總的來說,閉包對我們項目的性能提升還是有一定貢獻的,通過它可以實現(xiàn)一些強大的功能。特別是其回收不用的局部變量,避免內(nèi)存泄漏。所以有效的掌握還是很有必要的。

上一篇:unity3D中的C#編程入門

下一篇:Anddroid App和Java Web服務(wù)器間數(shù)據(jù)交互 之JavaWeb服務(wù)器搭建

熱點文章推薦
華清學(xué)員就業(yè)榜單
高薪學(xué)員經(jīng)驗分享
熱點新聞推薦
前臺專線:010-82525158 企業(yè)培訓(xùn)洽談專線:010-82525379 院校合作洽談專線:010-82525379 Copyright © 2004-2022 北京華清遠見科技集團有限公司 版權(quán)所有 ,京ICP備16055225號-5京公海網(wǎng)安備11010802025203號

回到頂部

主站蜘蛛池模板: av中文字幕无码免费看 | 又大又粗又猛免费视频 | 国产成人剧情AV麻豆果冻 | 日韩精品一区二区三区色欲AV | 无码少妇一区二区三区免费 | 色欲香天天天综合网站无码 | 资源av在线| 第四色播日韩AV第一页 | 各种高潮videos抽搐合集免费 | 亚洲成本人无码薄码区 | 久久无码高潮喷水抽搐 | 亚洲国产欧美日韩在线精品一区 | 欧美肥妇毛多水多BBXX | 精品国产SM捆绑最大网免费站 | 日产国产精品亚洲系列 | 亚洲av伊人久久青青草原 | 少妇高清性色生活片成人A片 | 玩弄放荡人妇系列AV在线网站 | 精品玖玖玖视频在线观看 | 无码毛片视频一区二区本码 | 午夜AV亚洲一码二中文字幕网址 | 亚洲精品久久无码午夜一区二区 | 精品一区二区三区无码免费视频 | 欧美黑人喷潮水xxxx | 国产精品卡1卡2卡3 亚洲成在人线aⅴ免费毛片 | 中文字幕一区二区三区中文字幕 | 看全色黄大色大片免费久久 | 亚洲熟妇无码一区二区三区 | 亚洲国产精品成人精品无码区在线 | 亚洲精品国男人在线视频 | 18禁在线播放点击进入 | 日本XXXXX片免费观看喷水 | 婷婷激情综合色五月久久图片 | 麻豆画精品传媒2021一二三区 | 美女脱内衣内裤摸屁屁 | 男人的天堂AV亚洲一区2区 | 国产成人无码A区在线观看导航 | 动漫AV纯肉无码AV在线播放 | 韩国三级A视频在线观看 | 欧洲美妇乱人伦视频网站 | 精品免费一区二区在线 |