【重溫基礎】19.閉包

本文是 重溫基礎 系列文章的第十九篇。
今日感覺:將混亂的事情找出之間的聯繫,也是種能力。 html

系列目錄:前端

本章節複習的是JS中的關於閉包,這個小哥哥呀,看看。 git

前置知識:
聲明函數兩種方法:github

  • 函數聲明,存在函數聲明提高,所以能夠在函數聲明以前調用(不會報錯)。
fun();  // ok
function fun(){};
  • 函數表達式,不存在函數聲明提高,若定義前調用,會報錯(函數還不存在)。
fun();  // error
var fun = function (){};

1.概念

2.1 詞法做用域

這裏先要了解一個概念,詞法做用域:它是靜態的做用域,是書寫變量和塊做用域的做用域**。segmentfault

function f (){
    var a = "leo";
    function g(){console.log(a)};
    g();
}
f(); // "leo"

因爲函數g的做用域中沒有a這個變量,可是它能夠訪問父做用域,並使用父做用域下的變量a,最後輸出"leo"數組

詞法做用域中使用的域,是變量在代碼中聲明的位置所決定的。嵌套的函數能夠訪問在其外部聲明的變量。微信

2.2 閉包

接下來介紹下閉包概念,閉包是指有權訪問另外一個函數做用域中的變量的函數閉包

閉包是由函數以及建立該函數的詞法環境組合而成。這個環境包含了這個閉包建立時所能訪問的全部局部變量。 函數

建立閉包的常見方式:在一個函數內建立另外一個函數。如:post

function f (){
    var a = "leo";
    var g = function (){
        console.log(a);
    };
    return g;// 這裏g就是一個閉包函數,能夠訪問到g做用域的變量a
}
var fun = f();
fun(); // "leo"

經過概念能夠看出,閉包有如下三個特徵:

  • 函數嵌套函數
  • 函數內部能夠引用函數外部的參數和變量
  • 參數和變量不會被垃圾回收機制回收

注:關於內存回收機制,能夠查看阮一峯老師的《JavaScript 內存泄漏教程》

另外,使用閉包有如下好處:

  • 將一個變量長期保存在內存中
  • 避免全局變量的污染
function f (){
    var a = 1; 
    return function(){
        a++;
        console.log(a);
    }
}
var fun = f();
fun(); // 2
fun(); // 3

由於垃圾回收機制沒有回收,因此每次調用fun()都會返回新的值。

  • 私有化成員,使得外部不能訪問
function f (){
    var a = 1;
    function f1 (){
        a++;
        console.log(a);
    };
    function f2 (){
        a++;
        console.log(a);
    };
    return {g1:f1, g2:f2};
};
var fun = f();
fun.g1(); // 2
fun.g2(); // 3

2.易錯點

2.1 引用的變量發生變化

function f (){
    var a = [];
    for(var i = 0; i<10; i++){
        a[i] = function(){
            console.log(i);
        }
    }
    return a;
}
var fun = f();
fun[0]();  // 10
fun[1]();  // 10
// ...
fun[10]();  // 10

本來照咱們的想法,fun方法中每一個元素上的方法執行的結果應該是1,2,3,...,10,而實際上,每一個返回都是10,由於每一個閉包函數引用的變量if執行環境下的變量i,循環結束後,i已經變成10,因此都會返回10
解決辦法能夠這樣:

function f (){
    var a = [];
    for(var i = 0; i<10; i++){
        a[i] = function(index){
            return function(){
                console.log(index);
                // 此時的index,是父函數做用域的index,
                // 數組的10個函數對象,每一個對象的執行環境下的index都不一樣
            }
        }(i);
    };
    return a;
};
var fun = f();
fun[0]();  // 0
fun[1]();  // 1
// ...
fun[10]();  // 10

2.2 this指向問題

var obj = {
    name : "leo", 
    f : function(){
        return function(){
            console.log(this.name);
        }
    }
}
obj.f()();  // undefined

因爲裏面的閉包函數是在window做用域下執行,所以this指向window

2.3 內存泄漏

當咱們在閉包內引用父做用域的變量,會使得變量沒法被回收。

function f (){
    var a = document.getElementById("leo");
    a.onclick = function(){console.log(a.id)};
}

這樣作的話,變量a會一直存在沒法釋放,相似的變量愈來愈多的話,很容易引發內存泄漏。咱們能夠這麼解決:

function f (){
    var a = document.getElementById("leo");
    var id = a.id;
    a.onclick = function(){};
    a = null;  //主動釋放變量a
}

經過把變量賦值成null來主動釋放掉。

3.案例

3.1 經典案例——定時器和閉包

代碼以下:

for(var i = 0 ; i<10; i++){
    setTimeout(function(){
        console.log(i);
    },100);
}

不出所料,返回的不是咱們想要的0,1,2,3,...,9,而是10個10
這是由於js是單進程,因此在執行for循環的時候定時器setTimeout被安排到任務隊列中排隊等候執行,而在等待過程當中,for循環已經在執行,等到setTimeout要執行的時候,for循環已經執行完成,i的值就是10,因此就打印了10個10
解決方法 :

  • 1.使用ES6新增的let

for循環中的var替換成let

  • 2.使用閉包
for(var i = 0; i<10 ; i++){
    (function(i){
        setTimeout(function(){
            console.log(i);
        }, i*100);
    })(i);
}

3.2 使用閉包解決遞歸調用問題

function f(num){
    return num >1 ? num*f(num-1) : 1;
}

var fun = f;
f = null;
fun(4)   // 報錯 ,由於最好是return num* arguments.callee(num-1),arguments.callee指向當前執行函數,可是在嚴格模式下不能使用該屬性也會報錯,因此藉助閉包來實現

這裏可使用return num >1 ? num* arguments.callee(num-1) : 1;,由於arguments.callee指向當前執行函數,可是在嚴格模式下不能使用,也會報錯,因此這裏須要使用閉包來實現。

function fun = (function f(num){
    return num >1 ? num*f(num-1) : 1;
})

這樣作,實際上起做用的是閉包函數f,而不是外面的fun

3.3 使用閉包模仿塊級做用域

ES6以前,使用var聲明變量會有變量提高問題:

for(var i = 0 ; i<10; i++){console.log(i)};
console.log(i);  // 變量提高 返回10

爲了不這個問題,咱們這樣使用閉包(匿名自執行函數):

(function(){
    for(var i = 0 ; i<10; i++){console.log(i)};
})()
console.log(i);  // undefined

咱們建立了一個匿名的函數,並當即執行它,因爲外部沒法引用它內部的變量,所以在函數執行完後會馬上釋放資源,關鍵是不污染全局對象。這裏i隨着閉包函數的結束,執行環境銷燬,變量回收。
可是如今,咱們用的更多的是ES6規範的letconst來聲明。

參考文章

  1. MDN 閉包
  2. 《JavaScript高級程序設計》

本部份內容到這結束

Author 王平安
E-mail pingan8787@qq.com
博 客 www.pingan8787.com
微 信 pingan8787
每日文章推薦 https://github.com/pingan8787...
JS小冊 js.pingan8787.com

歡迎關注微信公衆號【前端自習課】天天早晨,與您一塊兒學習一篇優秀的前端技術博文 .

相關文章
相關標籤/搜索