理解Js的閉包

閉包是Js的一個難點,也是它的一個特色,很多高級應用都要靠閉包來實現。

1.變量的作用域

要理解閉包,首先必須要理解Js特殊的變量作用域。

變量的作用域無非只有2種,全局變量局部變量

JavaScript語言的特殊之處還在于,函數內部可以直接讀取全局變量。


//  函數內部可以直接讀取全局變量

var n=100

function f1(){
    console.log(n)
}


f1()     // 100

上面代碼中,函數f1的內部,是可以直接讀取全局變量n的。


另一方面,函數外部自然不能讀取函數內部的局部變量。

//  函數外部不可以讀取函數內部變量



function f1(){
    var n=100
}

f1()

console.log(n)  //  Uncaught ReferenceError: n is not defined


如上面代碼,在函數f1外部去嘗試打印n的時候,就報錯。


那么如何從函數外部去讀取函數內部的變量呢?

出于種種原因,有時候我們需要從函數外部去讀取函數內部的變量。 但是前面說過了,正常情況下,這是辦不到的,那么就要使用變通的方法。

那就是在函數內部,再定義一個函數,并將其作為返回值

//  函數作為返回值

function f1(){
    var n=100

    return function bar(x){
        if(x>n){
            console.log(n)
        }
    }
}


var f=f1()

f(102)   // 100

上面的代碼中,我就打印出了函數內部變量n


2.閉包的概念

上面代碼中的bar函數,就是閉包。
閉包實際上就是指閉包函數,它是一個函數。

閉包實際上就是

能夠讀取其它函數內部變量的函數

切記,閉包是一個函數


3.閉包的用途

閉包可以用在很多方面。它最大的用處有2個。

讀取其它函數內部變量讓這些變量始終保持在內存中

請看下面的代碼

//  函數作為返回值

function f1(){
    var n=100

    nAdd=function(){
        n++
    }

    return function bar(){
        console.log(n)  
    }
}


var f=f1()

f()   // 100


nAdd()

f()  // 101

上面代碼中,f實際上就是閉包函數bar
它一共運行了2次,第一次的結果是100,第二次的結果是101。

這證明了函數f1中的變量一直保存在內存中。并沒有在f1的調用結束后被清除

為什么會這樣呢? 原因就在于,f1是bar的父函數,而子函數bar被賦值給了一個全新的全局變量f,而且bar的存在依賴于f1,因此f1也始終存在于內存中,不會因為f1的調用完成而被垃圾回收機制銷毀

這段代碼另一個值得注意的地方就在于nAdd=function(){n++}這段,首先,nAdd前面沒有使用var關鍵字,、因此nAdd是一個全局變量,而不是局部變量。 你看nAdd里面也能讀到其它函數內部的變量n,因此nAdd也是一個閉包函數。

所以nAdd相當于一個setter,可以在函數外部對函數內部的變量進行操控。


4.使用閉包注意點


  • 由于使用閉包的使用會使得函數中定義的變量都保存在內存中,內存消耗很大,所以不能濫用閉包。否則會造成網頁性能問題。

    解決辦法,是在退出函數之前,將不使用的局部變量全部刪除。


  • 閉包會在父函數外部,改變父函數內部變量的值。 所以如果你把父函數當做對象(object)來使用,把閉包當作它的共有方法來使用,把內部變量當作它的私有屬性來使用,這時一定要小心,不要隨便改變父函數內部的值。


5.例子


//  函數作為返回值


var name="The Window"

var object={
    name:"My object",
    getNameFunc:function(){
        console.log(this) // {name:"My object",getNameFunc:f}
        return function(){
            console.log(this)  // Window
            return this.name
        }
    }
}


console.log(object.getNameFunc()())  //  The Window

上面代碼中,this作為對象object的一個屬性被調用時,指向是object這個對象,但是再return一個函數的時候,this的指向就變成了全局window

函數的作用域是創建的時候確定的,不是在調用的時候,最內層那個函數創建的時候就是全局。 所以this指的是全局window


6. 請看下面這段代碼

//  函數作為返回值


function a(){
    var i=0;

    return b=()=>{
        console.log(++i)
    }
}


var c=a()

c()   //  1

上面代碼有2個特點:

1.函數b嵌套在函數a內

2.函數a返回函數b

引用關系如圖

閉包

這樣在執行完 var c=a()后,變量c實際上是指向了函數b,再執行c()就會打印i的值。

上面的代碼實際上就創建了一個閉包。

因為函數a外的變量c引用了函數a內部的函數b

就是說,

當函數a的內部函數b被函數a外的一個變量引用的時候,就創建了一個閉包

所謂“閉包”,就是在構造函數體內定義另外的函數作為目標對象的方法函數,而這個對象的方法函數反過來引用外層函數體中的臨時變量。這使得只要目標 對象在生存期內始終能保持其方法,就能間接保持原構造函數體當時用到的臨時變量值。盡管最開始的構造函數調用已經結束,臨時變量的名稱也都消失了,但在目 標對象的方法內卻始終能引用到該變量的值,而且該值只能通這種方法來訪問。即使再次調用相同的構造函數,但只會生成新對象和方法,新的臨時變量只是對應新 的值,和上次那次調用的是各自獨立的。
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容