我眼中的閉包

閉包幾乎是通往高級前端工程師所必須通過的一個門檻。在這方面看過不少資料,感受許多文章都只是提到了閉包的實現,而沒有涉及閉包的原理。前端

在談閉包以前須要先了解幾個相關的概念:瀏覽器

一、變量做用域:全局變量擁有全局做用域,在js代碼的任何地方均可見。在函數內部聲明的變量只在該函數體內可見,被稱爲局部變量。前端工程師

二、函數做用域:在函數內聲明的全部變量在該函數體內始終是可見的。閉包

三、做用域鏈:做用域鏈是一個對象。做用域鏈中定義了該做用域內的變量,它保證了該做用域中變量、函數的有序訪問。框架

四、做用域鏈的建立:定義一個函數時,會保存一個做用域鏈。調用一個函數時,會建立一個新的對象來存儲其局部變量,並將該對象添加至保存的做用域鏈上,同時建立一個新的更長的表示函數調用做用域的鏈。對於嵌套函數來講,每調用一次外部函數,都會建立一個新的做用域鏈,因此每次調用外部函數,雖然其內部代碼相同,可是調用的做用域鏈是不一樣的。函數

五、垃圾回收機制:js具備自動垃圾回收機制,會按期把再也不使用的變量銷燬,釋放其佔用的內存。(只會銷燬局部變量,全局變量的生命週期只有在頁面或瀏覽器關閉時纔會結束)對象

閉包的原理:函數的執行依賴於函數定義時的做用域鏈,即js函數執行時用的做用域鏈是該函數定義時建立的做用域鏈。大多數時候定義函數時的做用域鏈在函數執行時仍然有效。可是若是定義函數時的做用域鏈和執行函數時的做用域鏈不一樣時,就會出現問題。即當一個函數中嵌套了另外一個函數,並把嵌套的函數對象做爲返回值返回時,就會產生這種狀況。blog


定義函數的做用域鏈和執行時相同

外部函數把嵌套函數做爲返回值返回時

第一個例子很容易理解。可是第二個,能夠看到當外部函數把嵌套函數做爲返回值返回時,其執行結果仍然是局部變量的值,而不是全局變量的值。這就是由於函數執行時用到的做用域鏈,實際上是函數定義時建立的。無論該函數在什麼時候何地執行,它經過做用域鏈最早找到的變量就是相同外部函數中定義的變量。而閉包正是利用這種特性實現的:閉包能夠捕捉到局部變量或參數,並一直保存下來,使其不會被當成垃圾回收。生命週期

閉包和垃圾回收的關係:調用函數時,會建立一個對象來保存其局部變量,而且把這個對象添加到做用域鏈上。當函數返回時(即執行完成了),就會從做用域鏈中把綁定局部變量的對象刪除,這個對象就會被看成垃圾回收。可是若是定義了嵌套函數,那麼嵌套函數也會有本身相應的做用域鏈,它與外部函數同樣,把其局部變量保存到一個對象上,若是嵌套函數是做爲返回值返回的或者存儲在某處的屬性裏,那麼就會有一個外部引用指向這個嵌套函數,那麼外部函數就不會被看成垃圾回收,它在做用域鏈中綁定局部變量的對象也不會被回收。ip

閉包的應用場景:實現私有成員;保護命名空間;避免污染全局變量;可使變量長期駐留在內存中。這是在網上找的,不是特別好理解。其實能夠參考jQuery的源碼,它就是將全部的代碼封裝到了一個閉包裏邊的。下邊是我以前寫的一段代碼,再根據閉包進行的改寫:


改寫以前的代碼

根據閉包原理改寫後的代碼

在改寫後的代碼中,定義了2個閉包,這2個閉包共享同一個變量defaultSize。能夠看出使用閉包對代碼的封裝,避免全局變量的污染有很大的做用。

使用閉包的注意事項:固然閉包也不是十全十美,它能夠把局部變量長期保存到內存中,不被看成垃圾回收,這種特性若是使用不當,很容易會形成內存泄漏。建議仍是多看看其它比較成熟的框架是怎麼使用閉包的,會對提升我的代碼的質量有很大的幫助。

相關文章
相關標籤/搜索