關於js閉包我想說幾句

聲明:部份內容參考文章 Ice-shou:閉包詳解一javascript

一.什麼是閉包?

如下是三本比較權威的書對閉包的解釋java

《JavaScript高級程序設計》bash

閉包是指有權訪問另外一個函數做用域中的變量的函數;閉包

《JavaScript權威指南》異步

從技術的角度講,全部的JavaScript函數都是閉包:它們都是對象,它們都關聯到做用域鏈。函數

《你不知道的JavaScript》post

當函數能夠記住並訪問所在的詞法做用域時,就產生了閉包,即便函數是在當前詞法做用域以外執行。性能

說明ui

要搞懂什麼是閉包,必需要先搞明白如下幾點基礎知識spa

二.基礎知識:

1.變量的做用鏈

  • JavaScript變量有兩種:全局變量,局部變量

  • 局部變量做用域通常在函數裏面,在函數以外的視爲全局變量

  • 通常來講,在函數裏面能夠訪問全局的變量,在函數外面不能夠訪問函數裏面的變量

  • Javascript存在「鏈式做用域」結構(chain scope),這裏的鏈式做用域能夠理解爲函數嵌套,子對象會一級一級地向上尋找全部父對象的變量。因此,父對象的全部變量,對子對象都是可見的,反之則不成立.

    var str1="hello";//全局變量
    function fun(){
    	var res="i am coming";//fun()內的局部變量
    }
    function funa(){
    	var str2=" world";//funa()內的局部變量
    	function funb(){
            function func(){
                console.log(str1);//hello
                console.log(str2);//world
    			console.log(res);//報錯,undefined
            }
    		func();
    	} 
    	funb();
    }
    funa();
    console.log(str2);//報錯
    複製代碼

2.匿名函數的調用

  • 函數表達式調用法

    var exc=function(){
        console.log("hello world");
    }
    exc();
    複製代碼
  • 自調用

    (function(){
          console.log("hello world");
    })();
    複製代碼
  • 逐層調用

    function fun1(){
        var a=0;
        console.log(a);
        return function(){
            a++;
            console.log(a);
        }
    }
    //注意這種調用的結果:
    fun1();
    fun1()();
    fun1()();
    輸出
    0
    0
    1
    0
    1
    //解釋:每一次先執行fun1(),a都會初始化爲0,再執行匿名函數,a++獲得1
    複製代碼
  • 先賦值給一個變量再由變量調用

    function fun1(){
        var a=0;
        console.log(a);
        return function(){
            a++;
            console.log(a);
        }
    }
    //注意這種調用的結果:
    var res=fun1();
    res();
    res();
    res();
    輸出
    0
    1
    2
    3
    //解釋:fun1()只執行一次,因此a=0只執行一次,之後每次執行res()是在執行匿名函數,每執行一次,a自增一次
    複製代碼

三.分析閉包

閉包是指有權訪問另外一個函數做用域中的變量的函數;《JavaScript高級程序設計》

從技術的角度講,全部的JavaScript函數都是閉包:它們都是對象,它們都關聯到做用域鏈。

《JavaScript權威指南》

function fn1() {
	var str = 'hello,world';
	function fn2() {
		console.log(str);//能夠訪問fn1()函數
	}
	fn2();
}
fn1();
複製代碼

說明

對於上面兩本書對閉包的定義都比較迷,按照定義,fn2()函數就是一個閉包,,可是這就是閉包了嗎?實際上並不明顯,咱們看一個更加明顯的例子...

function fn1() {
	var str = 'hello world';
	function fn2() {
		console.log(str);
	}
	return fn2;
}
var fn3 = fn1();
fn3();
複製代碼

說明

  • fn2的詞法做用域能訪問fn1的做用域

  • 將fn2當作一個值返回

  • fn1執行後,將fn2的引用賦值給fn3

  • 執行fn3,輸出了變量str

當函數能夠記住並訪問所在的詞法做用域時,就產生了閉包,即便函數是在當前詞法做用域以外執行。

《你不知道的JavaScript》

function fun1(){
    var a=0;
    console.log(a);
    return function(){
        a++;
        console.log(a);
    }
}
//注意這種調用的結果:
var res=fun1();
res();
res();
res();
輸出
0
1
2
3
複製代碼

說明

  • 第一次執行var res=fun1(),把匿名函數的引用傳給了變量res

  • 每次執行res()即在調用匿名函數,注意調用的位置是在匿名函數做用域以外

  • 通常來講局部變量在函數執行以後就會別垃圾回收機制回收,可是調用res()以後變量a並無被回收,每執行一次res(),a的值自增一次

  • res()函數能夠記住並訪問原來所在的詞法做用域

四.總結

1.什麼是閉包

  • 一個能夠訪問其餘做用域的變量的函數
  • 閉包可以可以記住並訪問原來所在的詞法做用域

2.閉包的優勢和缺點

  • 優勢

    • 實現了能夠訪問其餘做用域變量,而且避免了全局變量對自身詞法做用域變量的污染

    • 能夠把局部變量(自身做用域的變量)駐留在內存中一直保存着上一次執行的值,不會被垃圾回收機制回收,從而避免使用全局變量

  • 缺點

    • 局部變量一直駐留在內存中不會被回收,致使內存被爆滿,影響程序性能

3.閉包缺點的解決方案

  • 建議在很是有必要的時候才使用閉包
  • 使用完變量肯定再也不使用, 將null賦值給變量,var a = null;

4.閉包的常見形式

上面我用了很多的例子來解釋什麼是閉包,不難發現閉包存在的形式,就是一個知足閉包定義的各類條件的函數,並且常以匿名函數的形式出現(注意:並非匿名函數都是閉包,兩者不能等同)

function fun1(){
    return function(){
        //閉包主體
    }
}
var res=fun1();
res();//閉包函數調用
複製代碼
(function(i){
    //閉包主體
})(i);//閉包函數自調用
複製代碼

5.閉包的應用的典型案例

  • 異步程序中避免因執行時間不一致致使變量丟失

    //打印1-10
    for (var i = 1; i <= 10; i++) {
    	setTimeout(function () {
    		console.log(i);
    	}, 1000);
    }
    //結果打印了10個11
    複製代碼

    緣由說明

    • i是聲明在全局做用中的,定時器中的匿名函數也是執行在全局做用域中,打印i值得時候向上逐層尋找變量
    • for循環執行的速度要遠比定時器執行的速度快,因此,定時器還將來得及打印i,for循環已經循環完畢,此時i的值是11,因此定時器打印出來的就是11

    解決

    上例很明顯就是在匿名函數裏面訪問全局變量,因爲異步緣由致使並未能準確打印出全局變量的值,因此解決方案就是循環i的時候,把i保存在私有做用域中而且一直保存,使用閉包來實現

    for (var i = 1; i <= 10; i++) {
    	(function (j) {
    		setTimeout(function () {
    			console.log(j);
    		}, 1000);
    	})(i);
    }
    複製代碼
相關文章
相關標籤/搜索