例項詳解js閉包(一)閉包基本概念及其作用推導
在學習前端的過程中,不可避免的要學習到js閉包這個知識點,很多朋友感到對閉包很難理解,也不清楚它有什麼用。本文就詳細介紹一下閉包,並通過幾個小例子來說明下閉包的用處。
一、閉包的概念
閉包的英文單詞是Closure,我先給閉包可以這樣下個簡單的定義,這個定義不是官方的,是我自己理解的。
定義:如果在函式A的內部,聲明瞭另外一個函式B,並且函式B可以訪問A中定義的變數或是資料,此時函式A和函式B就形成了閉包。
閉包其實講述了函式與函式的關係。
二、閉包的基本形式
我們直接來個例子加以說明:
例1:閉包基本形式
這裡定義了一個函式f1,在f1的內部又定義了一個區域性變數num和函式f2,並且f2呼叫了局部變數num,這個程式碼結構已經形成了閉包。
不過,這樣的程式碼看看也罷,貌似是沒有任何作用的。我們再改改。
例2.閉包基本形式2
檢視執行結果,我們看到輸出了10 。 我們下一個簡單的結論:閉包可以讓一個區域性的變數,在它的作用域之外訪問到。您可能不同意我這個結論。不過沒關係,請繼續往下看。
三、閉包的模式
閉包其實有2種模式:
1.函式形式的閉包
2.物件形式的閉包
咱們剛才在上邊的例子都是函式形式的閉包。我們舉一個物件模式的閉包,作為了解。
例3.物件模式的閉包
這裡函式f1和它內部的物件obj形成了閉包,原因是:1.obj是在f1內部宣告的,2.obj的age屬性訪問了f1內部宣告的區域性變數num。
好了,最常見的閉包,還是函式模式的,所以這個瞭解就好,我們下面的例子都是以函式模式來講解的。
四、閉包的作用
閉包有什麼用呢?它的作用在於兩點:1.延長區域性變數的作用域鏈。2.快取資料。其實第2點就是通過第1點來達到的效果。我們舉個例子。
例4.閉包作用演示1
我們前面的例子僅僅是在f1的內部聲明瞭一個f2,而這裡,我們不僅在f1內部聲明瞭一個函式,並且把這個內部的函式作為外部函式f1的返回值return了回來。
這下就有趣了。當我們執行語句:var fun = f1();的時候,變數fun裡儲存的是什麼?當然是f1函式的返回值,只不過這時候的返回值恰好又是一個函式物件。其實在這裡,就fun相當於是一個函式表示式了。這條語句的呼叫結果,等價於下面這種寫法
例5.簡單函式表示式語法
這就是一個簡單的函式表示式,既然如此,fun當然可以通過fun()的形式來呼叫這個函式。不過這都不是重點。
重點是,我們在全域性作用域下,通過fun這個引用,訪問到了f1裡定義的區域性變數num。
你如果覺得不是,你再想想,我是不是能這樣:
例6.閉包作用演示2
修改過後,是不是已經說明了問題,什麼問題?我們確實在全域性作用域下,拿到了一個函式內部宣告的區域性變數的值。或者換句話說,我們在函式外部訪問到了內部宣告的區域性變數。
看到這裡,你可能還是覺得有點迷糊,我是訪問到了,但是這和在內部函式中直接console.log()輸出這個區域性變數num的值有什麼區別呢?你說的訪問,也僅僅是讀而已,並不能代表能操作它,比如改變它的值,所以你並不認為例6是真正的在外部訪問了內部的資料。您是不是也有如此的疑惑呢?
下面再看一組例子,為您解惑。
例7.非閉包的資料訪問操作
請問,此例中程式碼執行結果是什麼?答案是輸出3次11 。 這個原因很簡單,3次完全獨立的函式呼叫而已,所以每次呼叫的時候都會開闢一個全新的記憶體空間來儲存f1中宣告的區域性變數num,並且賦初值為10,那麼++過後必然都是11 。所以這個輸出結果毫無懸念。
再看這個例子的變形,也就是例8
例8.閉包版的資料訪問操作
先說輸出結果吧,3次呼叫,輸出的是
11,12,13 這個結果奇怪嗎?不奇怪嗎? 這就是閉包的作用。
在程式碼的第27行,我們聲明瞭一個變數fun,給他複製為f1()函式執行的返回值,也就是內部宣告的那個函式物件。接下來3次呼叫都是使用的同一個物件,而這個fun指向的函式物件內部訪問了定義它的外部函式f1宣告的一個區域性變數num。所以,3次呼叫fun()時,操作的num++,都是針對記憶體裡的同一個變數進行的++,所以我們看到的結果就是11,12,13 。下面通過一個圖來說明下例8
說明下圖上表示的意思。
1.黑色的大矩形框,表示程式碼執行時的js環境
2.紅色矩形框代表js引擎執行緒在執行程式碼是的記憶體空間。當然這個程式碼在執行時是有先後的,記憶體也會有先後的變化,不過我為了簡單起見,把所有的過程都畫一張圖裡了。
3.小的黑色矩形框表示在預解析時,已經載入到記憶體中的f1函式的程式碼
那麼執行結果是這樣的,首先執行var fun = f1(); 這時候會把小黑方框中的程式碼載入到主執行緒去執行,執行的結果就是得到那2個藍色的矩形框。
小一點的是那個num變數,大一點的藍色矩形框代表那個返回的函式物件。從這張圖我們能清晰的看到,整個程式碼在執行時,只有1個函式物件fun,也只有一個變數num。由於num被fun物件所引用,所以,雖然超出了它的作用域,它也無法釋放掉。這也證明了,我們說的,閉包延長了變數的作用域鏈。
現在我們可以得出結論了,閉包的作用就是:
1.延長變數的作用域鏈。
2.快取資料