JavaScript閉包中難點深入分析

初識閉包

閉包可謂是JS的一大難點也是面試中常見的問題之一,今天開始梳理一下閉包的知識,請諸君品鑒。

什麼是閉包

閉包是嵌套的內部函數;內部函數包含被引用變量(函數)的對象。閉包存在於嵌套的內部函數中,例如在javascript中,隻有函數內部的子函數才能讀取局部變量,所以閉包可以理解成“定義在一個函數內部的函數“。在本質上,閉包是將函數內部和函數外部連接起來。當然如何直觀的查看閉包可以通過chrome來查看,這裡有個坑需要反饋一下,新版的chrome需要先調用fun2()才允許debugger,這樣才能顯示閉包。

<script>
    function fn1(){
        var a = 2;
        function fn2(){//執行函數定義就會產生閉包(不用調用內部函數)
            console.log(a);
        }
        //新版的chrome需要返回一下內部函數才會顯示閉包
        return fn2()
    }
    fn1()
</script>

如何產生閉包

當一個嵌套的內部(子)函數引用瞭嵌套的外部(父)函數的變量(函數)時,就產生瞭閉包

<script>
    // 將函數作為另一個函數的返回值
    function fn1(){
        var a = 2;
        function fn2(){//
            a++;
            console.log(a);
        }
        return fn2 //將一個內部函數作為一個外部函數的返回值返回
    }
    var f = fn1()
    //整個過程產生瞭一個閉包,主要看你產生瞭幾個內部函數對象,調用瞭幾次外部函數
    //閉包的特點就是函數內部的變量會一直存在於內存中,不會立即釋放。
    f()//3   這裡的f()是調用瞭內部函數
    f()//4
</script>

<script>
    // 將函數作為實參傳遞給另一個函數調用
    function showDelay(msg,time){
        //setTimeout 的第一個參數是函數,符合閉包的規則
        setTimeout(function(){
            alert(msg)
        },time)
    }
    showDelay('張三',2000)
</script>

產生閉包條件

函數嵌套;內部函數引用瞭外部函數的數據(變量/函數)。

閉包的作用

使用函數內部的變量在函數執行完畢後,仍然存活在內存中(延長瞭局部變量的生命周期);讓函數外部可以操作(讀寫)到函數內部的數據(變量/函數)。

閉包的生命周期

產生:在嵌套的內部函數定義執行完時就產生瞭(不是在調用),死亡:在嵌套的內部函數稱為垃圾對象時就死亡瞭。

<script>
    function fn1 () {
        //此時閉包就已經產生瞭(函數提升,內部函數對象已經創建瞭)
        var a = 2;
        function fn2 () {//
            a++;
            console.log(a);
        }
        return fn2 //將一個內部函數作為一個外部函數的返回值返回
    }
    var f = fn1()
    f()//3   
    f()//4
    f = null //閉包死亡(包含閉包的函數對象成為垃圾對象)
</script>

閉包的應用

定義JS模塊(具有特定功能的js文件),將所有的數據和功能都封裝在一個函數的內部(私有的),隻向外暴露一個包含n個方法的對象和函數;模塊的使用者隻需要通過模塊暴露的對象調用方法來實現對應的功能。

//myModule.js 文件
function myModule(){
    // 私有數據
    var msg = 'My Module'
    function showUpper(){
        console.log('showUpper' +msg.toUpperCase());
    }
    function showLow(){
        console.log('showLow' +msg.toLowerCase());
    }
    //向外暴露對象(給外部使用的方法)
    return {
        showUpper:showUpper,
        showLow:showLow
    }
}
//index.html文件
<script src="./myModule.js"></script>
<script>
    var module = myModule()
    module.showUpper()
    module.showLow()
</script>
 

我們也可以通過匿名函數來實現閉包,這樣能很便捷的調用閉包裡面的屬性,雖然會達到我們想要的效果,但是可能會造成全局的變量名污染,建議使用第一種。

//myModule2.js文件
(function(){
    // 私有數據
    var msg = 'My Module'
    // 操作數據的函數
    function showUpper(){
        console.log('showUpper' +msg.toUpperCase());
    }
    function showLow(){
        console.log('showLow' +msg.toLowerCase());
    }
    //向外暴露對象(給外部使用的方法)
    window.myModule2 = {
        showUpper:showUpper,
        showLow:showLow
    }
})()
//index.js文件
<script src="./myModule2.js"></script>
<script>
    myModule2.showUpper()
    myModule2.showLow()
</script

閉包的缺點及解決方法

在我們使用閉包過程中,函數執行完後,函數內部的局部變量沒有釋放,占用內存時間會變長,容易造成內存泄漏,所以在日常開發中,盡量避免閉包的出現,或者要對局部變量及時釋放。

<script>
    function fn1(){
        var arr = new Array[100000]
        function fn2(){
            console.log(arr.length);
        }
        return fn2
    }
    var f = fn1()
    f()
    //不用閉包或者回收閉包
    f = null//讓內部函數成為垃圾對象 --> 回收閉包
</script>

內存溢出:一種程序運行出現的錯誤,當程序運行需要的內存超過瞭剩餘的內存時,就會拋出內存溢出的錯誤。

<script>
    var obj = {}
    for(var i=0;i<10000;i++){
        obj[i]=new Array(1000000)
        console.log('------');
    }
</script>

內存泄漏:占用的內存沒有及時釋放,內存泄漏積累多瞭就容易導致內存溢出。常見的內存泄漏:意外的全局變量、沒有及時清理的計時器或回調函數、閉包。

<script>
    //意外的全局變量
    function fn(){
        a = 10;
        console.log(a);
    }
    // 調用函數雖然能打印a,但是a並沒有被釋放掉。一不註意就設置瞭一個全局變量
    fn()
    //沒有及時清理計時器或回調函數
    var intervalId = setInterval(function(){ //啟動循環定時器後不清理
        console.log('--------');
    },2000)
    // clearInterval(intervalId)
    //閉包
    function fn1(){
        var a = 2 //閉包 a 並沒有被釋放掉
        function fn2(){
            console.log(++a)
        }
        return fn2
    }
    var f = fn1()
    f()
    // f = null 不執行這條語句,a的值一直在
</script>

閉包案例

<script>
    // 案例一:
    var name = "this is Window"
    var object = {
        name:"this is Object",
        getName:function(){
            return function(){
                return this.name
            }
        }
    }
    //閉包的this隻能是全局,若在當前作用域中定義瞭this,就直接使用定義的this,若沒定義,則需要一層層向外找,直到全局為止
    //本題是沒有閉包的
    alert(object.getName()())//this is Window
    // 案例二:
    var name1 = "this is Window"
    var object1 = {
        name1:"this is Object",
        getName:function(){
            //定義的that形成瞭閉包,內部函數引用瞭外部函數的變量,而this指向的是object,所以返回的是object中的name1
            var that = this;
            return function(){
                return that.name1
            }
        }
    }
    alert(object1.getName()())// this is Object
</script>
<script>
    //沒有使用閉包的話,數據是沒有保留的,所以n傳遞給o之後,下次運算o值還是上次的值不會發生改變
    function fun(n,o){
        console.log(o);
        return{
            fun:function(m){
                return fun(m,n)
            }
        }
    }
    //在執行fun(0)之後,n被之前的n=0,一直被調用
    var a = fun(0); //閉包裡面的n傳入瞭0
    a.fun(1); 
    a.fun(2);
    a.fun(3)//undefined,0,0,0
    //鏈式執行會導致n的改變,n是前面函數執行的形參
    var b = fun(0).fun(1).fun(2).fun(3)//undefined,0,1,2
    //c.fun(2)、c.fun(3)都調用瞭fun(1)留下的閉包n
    var c = fun(0).fun(1); 
    c.fun(2); 
    c.fun(3)//undefined,0,1,1
</script>

到此這篇關於JavaScript閉包中難點深入分析的文章就介紹到這瞭,更多相關JS閉包內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: