當前位置:
首頁 >
前端技术
> javascript
>内容正文
javascript
也谈闭包--小白的JS进阶之路
生活随笔
收集整理的這篇文章主要介紹了
也谈闭包--小白的JS进阶之路
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
JavaScript當然是會用的,不過沒有深入系統的學習罷了。平常還是用JQuery比較多,原生的JS用到的很少。
不過前端時間學習Ruby,被動態語言的強大和魔幻給震驚了一把。了解Ruby后,我把目光轉移到了這門很早就伴隨我的語言。JQuery是一個華麗的外衣,一把好用的工具,但炫彩背后必有強大的后臺,讓我稍微掀起這幕布的一角吧。###作用域鏈
閉包這個詞想必大家不陌生,可謂是動態語言了不起的幾把刷子之一了。在JS中,要想理解閉包,首先要拿作用域鏈這個概念下手。
先來看一下JS語言的另一種定義:JS是基于詞法作用域的語言--**通過閱讀包含變量定義在內的源碼就能知道變量的作用域。**全局變量在程序中始終是有定義的,**局部變量在聲明它的函數體內以及所嵌套的函數內始終是有定義的。**
在頂層代碼中,作用域鏈由一個全局對象組成。在不包含嵌套的函數體內,作用域鏈由兩個對象,第一個是定義函數參數和局部變量的對象,第二個是全局對象。在一個嵌套的函數體內,作用域鏈上至少有三個對象。
**當定義一個函數時,它實際上保存一個作用域鏈(*很重要,后面靠它混飯了,注意是定義的時候哦*)。**當調用函數時,它創建一個新的對象來儲存它的局部變量,并將這個對象添加至保存的那個作用域鏈上,同時插進一個新的更長的表示函數調用作用域的鏈。
對于嵌套函數來說,每次調用外部函數時,內部函數又會重新定義一遍。這段定義看完之后,估計要徹底暈了。沒關系,先對作用域鏈有個印象即可,后面才是大菜###閉包定義
函數對象可以通過作用域鏈相互關聯,函數體內部的變量都可以保存在函數作用域內,這種特性在計算機科學文獻中叫“閉包”。
###JS閉包
JS采用詞法作用域,也就是說,函數的執行依賴于變量作用域,**這個作用域是函數定義時決定的,而不是函數調用時決定的**。
為了實現語法作用域,JS函數對象內部狀態引用當前的作用域鏈。
從技術角度看,所有JS函數都是閉包:它們都是對象,它們都關聯到作用域鏈。
當調用函數時閉包所指向的作用域鏈和定義函數時的作用域鏈不是同一個作用域鏈時,事情就變得非常微妙。當一個函數嵌套了另外一個函數,外部函數將嵌套的函數對象作為返回值返回的時候往往會發生這種事情。很多強大的編程技術都利用了這類嵌套的函數閉包,以至于這種編程模式在JS中非常常見。
呼呼,這段話看完估計同志們又有點丈二和尚了,學術語言害死人啊。不過還是那句話:沒關系,繼續看。JS沒有密不透風的墻啊。###嵌套函數的語法作用域
理解閉包首先要理解嵌套函數的語法作用域規則,看一下這段代碼:
```
var scope = "global scope";
function checkscope(){var scope = "local scope";function f() { reutrn scope; };return f();
}
checkscopre() //"local scope"
```
因為返回的是f()的執行結果,所以對結果我們沒有疑問。
稍微改動一下這個函數:
```
var scope = "global scope";
function checkscope(){var scope = "local scope";function f() { reutrn scope; };return f;
}
checkscopre()() //?
```
考慮一下哦,這個結果如何呢?`checkscopre()`返回一個函數,然后我們再執行這個返回的函數,那么很多人認為返回結果就是`"global scope"`嘍。
餓,這其實就是作用域鏈的陷阱了。回想詞法作用域的基本規則:JS函數的執行用到作用域鏈,這個作用域鏈是函數定義時創建的。 所以不管何時執行函數f(),這種綁定在執行f()時依然有效。創建時定義的作用域鏈scope的值是`"local scope"`,那么因此返回結果也是`”local scope”`。
簡單來說,閉包這個特性讓人吃驚:它們可以捕捉到局部變量(和參數),并一直保持下來,看起來像這些變量綁定到了在其中定義它們的外部函數*(有點繞,建議閱讀3遍)*。###解惑詞法作用域規則
如果理解了詞法作用域的規則,就很容易理解閉包:定義大多數函數時的作用域鏈在調用函數時依然有效。
理解閉包的困難在于:外部函數中定義的局部變量在函數返回后就不存在了,那么嵌套的函數如何能調用不存在的作用域鏈呢?
想搞清楚這個問題,就需要深入了解類似C語言這種更底層的編程語言,并了解基于棧的CPU架構:如果一個函數的局部變量定義在CPU棧中,那么函數返回時它們確實不存在了。 但回想一下我們是如何定義作用域鏈的。我們將作用域鏈描述為一個對象列表,而不是一個棧。每次調用JS函數,都會創建新的對象保持局部變量,把這個對象添加至作用域鏈中。當函數返回,就刪除作用域鏈中的綁定變量的對象。如果不存在嵌套函數,也沒有其他引用指向這個綁定變量的對象,它就會被垃圾回收。如果定義了嵌套函數,每個嵌套函數各自對應一個作用域鏈,并且這個作用域鏈指向一個變量綁定對象。
如果這個嵌套函數在外部函數中保存下來,那么它們會和指向變量綁定對象一樣被垃圾回收。
但如果外部函數將嵌套函數作為返回值返回或儲存在某個屬性里,這時就有一個外部引用指向這個嵌套函數。它就不會被當做垃圾回收,所以它指向的變量綁定對象也不會被當做垃圾回收。
**看到這里相比對閉包有一定了解了,撥云見月時機不遠矣。下面通過幾個例子練練手,想必閉包從此就是囊中之物嘍。**
###閉包實現計數器
先看一個不健全的計數器實現:
```
uniqueInteger.counter = 0;
function uniqueInteger() {return uniqueInteger.counter++;
}
```
這里函數作為對象被賦予了屬性`counter `,調用`uniqueInteger()`,就是一個簡單的計數器。
但明顯的缺陷是:如果惡意的把`counter`重置或賦值給它一個非整數,就會導致函數錯誤。
利用閉包很好的解決這個問題,只要把計數器私有就可以了:
```
var uniqueInteger = (function() {var counter = 0;return function() { return counter++; };}());
```
`uniqueInter`就是一個嵌套函數,而`counter`則變成了私有的局部變量。因為第一次已經執行過了外部函數,所以當調用`uniqueInteger()`的時候,實際調用的是嵌套函數,這樣`counter`就訪問不到,但我們同時實現了功能。
像`counter`一樣的私有變量不是只能用在一個單獨的閉包內,在同一個外部函數內定義的多個嵌套函數也可以訪問它,這多個嵌套函數都共享一個作用域鏈:
```
function counter() {var n = 0;reutrn {count : function() { return n++; };reset : function() { n = 0; };};
}
var c = counter() , d = counter();
c.count() //0
d.count() //0,互不干擾
c.reset() //0
c.count() //0
d.count() //1,因為沒有重置d
```###閉包的不當用法
閉包如此美麗,以至于人們如此愛你。但別忘了玫瑰都帶刺啊,下面看看閉包使用不當帶來的意料之外的后果。
我們先來看一下正確的用法:
```
function constfunc(v) { return function() { return v; }; }var funcs = [];
for(var i = 0; i < 10; i++) funcs[i] = constfunc(i);funcs[5]() //5
```
我們稍微變一下形式,看看寫類似代碼往往會犯的一個錯誤:
```
function constfuncs() {var funcs = [];for(var i = 0; i < 10; i++)funcs[i] = function() { return i; };return funcs;
}
var funcs = constfuncs();
funcs[5](); //?
```
上面的閉包都是在同一個函數調用中定義的,因此他們可以共享變量。當constfuncs()返回時,i=10,所有閉包都共享這個值。因此,數組中函數返回值都是同一個值,這并不是我們想要的。
如果不理解,回想一下詞法作用域的定義哦。
###后記
至此,閉包就講完了。可能還是感覺有不理解的地方,如果是這樣,那么建議把不理解的地方多讀幾遍就會明白了。
我是一個小白,但是我也想進步哈!
我是一個小白,但是我也想進步哈!
轉載于:https://www.cnblogs.com/china-li/p/3557096.html
總結
以上是生活随笔為你收集整理的也谈闭包--小白的JS进阶之路的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: PHP命名空间(Namespace)的使
- 下一篇: [C#]最简单的Base64加密解密