前端開發:javascript閉包入門理解
案例
首先看下閉包的一個簡單案例
- function A(){
- function B(){
- console.log("Hello haorooms!");
- }
- return B;}var c = A();c();//Hello haorooms!
這個例子是一個比較簡單的閉包,解釋如下:
(1)定義了一個普通函數A
(2)在A中定義了普通函數B
(3)在A中返回B(確切的講,在A中返回B的引用)
(4)執行A(),把A的返回結果賦值給變量 c
(5)執行 c()
當一個內部函數被其外部函數之外的變量引用時,就形成了一個閉包。
閉包的作用
在上述例子中,B定義在A中,因此B依賴于A,而外部變量 c 又引用了B, 所以A間接的被 c 引用,也就是說,A不會被回收,會一直保存在內存中。為了證明我們的推理,上面的例子稍作改進。
- function A(){
- var count = 0;
- function B(){
- count ++;
- console.log(count);
- }
- return B;}var c = A();c();// 1c();// 2c();// 3
count是A中的一個變量,它的值在B中被改變,函數B每執行一次,count的值就在原來的基礎上累加1。因此,A中的count一直保存在內存中。
這就是閉包的作用,有時候我們需要一個模塊中定義這樣一個變量:希望這個變量一直保存在內存中但又不會“污染”全局的變量,這個時候,我們就可以用閉包來定義這個模塊。
常見的閉包寫法
前面我的文章中多次提及“自調用匿名函數”,大部分自調用匿名函數都是閉包的一種應用和寫法。
例如下面的例子
- (function(document){
- var viewport;
- var obj = {
- init:function(id){
- viewport = document.querySelector("#"+id);
- },
- addChild:function(child){
- viewport.appendChild(child);
- },
- removeChild:function(child){
- viewport.removeChild(child);
- }
- }
- window.jView = obj;})(document);
以上代碼可以改寫成如下代碼:
- var f = function(document){
- var viewport;
- var obj = {
- init:function(id){
- viewport = document.querySelector("#"+id);
- },
- addChild:function(child){
- viewport.appendChild(child);
- },
- removeChild:function(child){
- viewport.removeChild(child);
- }
- }
- window.jView = obj;};f(document);
在這段代碼中似乎看到了閉包的影子,但 f 中沒有任何返回值,似乎不具備閉包的條件,注意這句代碼:
- window.jView = obj;
obj 是在 f 中定義的一個對象,這個對象中定義了一系列方法, 執行window.jView = obj 就是在 window 全局對象定義了一個變量 jView,并將這個變量指向 obj 對象,即全局變量 jView 引用了 obj 。而 obj 對象中的函數又引用了 f 中的變量 viewport ,因此 f 中的 viewport 不會被回收,會一直保存到內存中,所以這種寫法滿足閉包的條件。 另外,我們把obj賦值給window.jView ,那么,我們在整個window中可以直接調用obj 中的函數,但是函數內部的變量不會被收回,例如我們調用obj的init函數可以這么寫:
- window.jView.init("haorooms")
小結
以上是對閉包最簡單的理解。當然閉包還有更深入的理解。例如執行環境(execution context)、活動對象(activation object)以及作用域(scope)和作用域鏈(scope chain)的運行機制等等。當然,我們理解閉包先從簡單開始,寫的多了,理解的就越深了。本文也是對閉包的最簡單的入門,希望通過這篇文章,大家對閉包有了一定的理解。
【本文為51CTO專欄作者“謝軍”的原創稿件,轉載可通過作者微信公眾號(jingfeng18)獲取聯系】