關於javascript閉包我想說幾句

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

一.前言

在咱們的開發當中會常常用到閉包,由於他確確實實解決了一些問題,在面試的時候,也會常常被問到對閉包的理解,然而實際上閉包的概念並無統一的說法,但不論是怎麼描述的,它的核心都是那樣,下面就來探究一下閉包究竟是怎麼理解的java

二.基礎知識:

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.匿名函數的調用

  • 函數表達式調用法post

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

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

    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
    複製代碼
  • 先賦值給一個變量再由變量調用spa

    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高級程序設計》

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

--《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》是怎麼描述的

--《你不知道的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.閉包的優勢和缺點

  • 優勢

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

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

  • 缺點

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

4.閉包缺點的解決方案

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

5.閉包的常見形式

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

形式一:

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

形式二:

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

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

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

//打印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);
}
複製代碼
相關文章
相關標籤/搜索