1. 程式人生 > >例項詳解js閉包(一)閉包基本概念及其作用推導

例項詳解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.快取資料