技術頻道導航
HTML/CSS
.NET技術
IIS技術
PHP技術
Js/JQuery
Photoshop
Fireworks
服務器技術
操作系統(tǒng)
網(wǎng)站運營

贊助商

分類目錄

贊助商

最新文章

搜索

一個示例一行行代碼理解JS閉包是如何執(zhí)行的

作者:admin    時間:2022-6-7 17:19:34    瀏覽:

JavaScript 中的閉包是許多人難以理解的概念之一。在接下來的文章中,我將清楚地解釋什么是閉包,并且我將使用簡單的代碼示例來說明這一點。

什么是閉包?

閉包是 JavaScript 中的一項功能,其中內部函數(shù)可以訪問外部(封閉)函數(shù)的變量——作用域鏈。

閉包具有三個作用域鏈:

  • 它可以訪問自己的范圍——在大括號之間定義的變量
  • 它可以訪問外部函數(shù)的變量
  • 它可以訪問全局變量

對于新手來說,這個定義似乎不好理解。不過沒關系,下面會通過簡單的示例說明,新手也可很快理解它。

真正的閉包是什么?

讓我們看一個 JavaScript 中的簡單閉包示例:

function outer() {
   var b = 10;
   function inner() {
        
         var a = 20; 
         console.log(a+b);
    }
   return inner;
}

這里我們有兩個函數(shù):

  • outer()是具有變量b的外部函數(shù),并返回inner函數(shù)。
  • inner()是一個內部函數(shù),它的變量a被調用,并在其函數(shù)體內訪問outer()的一個變量b

變量b的作用域僅限于outer函數(shù),變量a的作用域僅限于inner函數(shù)。

現(xiàn)在讓我們調用outer()函數(shù),并將結果存儲在一個變量X中。然后我們再次調用outer()函數(shù)并將其存儲在變量Y中。

function outer() {
   var b = 10;
   function inner() {
        
         var a = 20; 
         console.log(a+b);
    }
   return inner;
}
var X = outer(); //outer() 第一次調用
var Y = outer(); //outer() 第二次調用

讓我們一步一步地看看outer()函數(shù)第一次被調用時會發(fā)生什么:

  • 變量b已創(chuàng)建,其范圍僅限于outer()函數(shù),其值設置為10。
  • 下一行是一個函數(shù)聲明,所以沒有什么要執(zhí)行的。
  • 在最后一行,return inner查找名為inner的變量,發(fā)現(xiàn)該變量inner實際上是一個函數(shù),因此返回整個函數(shù)體inner。
  • [請注意,該return語句不執(zhí)行內部函數(shù), 一個函數(shù)僅在后跟()時執(zhí)行,而是該return語句返回函數(shù)的整個主體。]
  • return 語句返回的內容存儲在X中,因此,X將存儲以下內容:
    function inner() {
      var a=20;
      console.log(a+b);
    }
  • 函數(shù)outer()執(zhí)行完畢,現(xiàn)在outer()范圍內的所有變量都不存在了。

最后一部分很重要,需要理解。一旦函數(shù)完成執(zhí)行,在函數(shù)范圍內定義的任何變量都將不復存在。

在函數(shù)內部定義的變量的生命周期就是函數(shù)執(zhí)行的生命周期。

這意味著在console.log(a+b)中,變量b僅在outer()函數(shù)執(zhí)行期間存在。一旦outer函數(shù)完成執(zhí)行,變量b就不再存在。

當函數(shù)第二次執(zhí)行時,函數(shù)的變量會被再次創(chuàng)建,直到函數(shù)完成執(zhí)行。

因此,當outer()第二次調用時:

  • 創(chuàng)建了一個新變量b,其范圍僅限于outer()函數(shù),其值設置為10。
  • 下一行是一個函數(shù)聲明,所以沒有什么要執(zhí)行的。
  • return inner返回整個函數(shù)體inner。
  • return 語句返回的內容存儲在Y中。
  • 函數(shù)outer()執(zhí)行完畢,現(xiàn)在outer()范圍內的所有變量都不存在了。

這里重要的一點是,當outer()第二次調用函數(shù)時,b會重新創(chuàng)建變量。此外,當outer()函數(shù)第二次完成執(zhí)行時,這個新變量b再次不復存在。

這是要實現(xiàn)的最重要的一點。函數(shù)內部的變量只有在函數(shù)運行時才存在,一旦函數(shù)執(zhí)行完畢就不再存在。

現(xiàn)在,讓我們回到我們的代碼示例,看看XY。由于outer()函數(shù)在執(zhí)行時返回一個函數(shù),因此變量XY是函數(shù)。

這可以通過在 JavaScript 代碼中添加以下內容來輕松驗證:

console.log(typeof(X)); //X 是類型函數(shù)
console.log(typeof(Y)); //Y 是類型函數(shù)

由于變量XY是函數(shù),我們可以執(zhí)行它們。在 JavaScript 中,可以通過()在函數(shù)名稱后添加來執(zhí)行函數(shù),例如X()Y()。

function outer() {
var b = 10;
   function inner() {
        
         var a = 20; 
         console.log(a+b);
    }
   return inner;
}
var X = outer(); 
var Y = outer(); 
// outer()函數(shù)執(zhí)行完畢
X(); // X() 第一次調用
X(); // X() 第二次調用
X(); // X() 第三次調用
Y(); // Y() 第一次調用

demodownload

當我們執(zhí)行X()Y()時,我們本質上是在執(zhí)行inner函數(shù)。

讓我們逐步檢查X()第一次執(zhí)行時會發(fā)生什么:

  • 創(chuàng)建了變量a,并將其值設置為20。
  • JavaScript 現(xiàn)在嘗試執(zhí)行a + b,JavaScript 知道a的存在,因為它剛剛創(chuàng)建它。但是,變量b不再存在。由于b是外部函數(shù)的一部分,b因此僅在outer()函數(shù)執(zhí)行時存在。由于outer()函數(shù)在我們調用X()之前就完成了執(zhí)行,因此outer函數(shù)范圍內的任何變量都不再存在,因此變量b也不再存在。

由于 JavaScript 中的閉包,該inner函數(shù)可以訪問封閉函數(shù)的變量。換句話說,inner函數(shù)在執(zhí)行封閉函數(shù)時保留封閉函數(shù)的作用域鏈,因此可以訪問封閉函數(shù)的變量。

在我們的示例中,inner函數(shù)保存了outer()函數(shù)執(zhí)行b=10時的值,并繼續(xù)保存(關閉)它。

它現(xiàn)在引用它的作用域鏈,并注意到b在其作用域鏈中確實具有變量的值,因為它在outer函數(shù)執(zhí)行b時將值封閉在閉包中。

因此,JavaScript 知道a=20b=10,并且可以計算a+b。

你可以通過在上面的示例中添加以下代碼行來驗證這一點:

function outer() {
var b = 10;
   function inner() {
        
         var a = 20; 
         console.log(a+b);
    }
   return inner;
}
var X = outer(); 
console.dir(X); //使用 console.dir() 代替 console.log()

在控制臺,你可以展開元素以實際查看閉包元素(如下面倒數(shù)第四行所示)。請注意,即使在outer()函數(shù)完成執(zhí)行后, 閉包的值b=10也會保留。

 變量 b=10 保存在閉包中
變量 b=10 保存在閉包中

現(xiàn)在讓我們重新回顧一下我們在開始時看到的閉包的定義,看看它現(xiàn)在是否更有意義。

所以內部函數(shù)有三個作用域鏈:

  • 訪問自己的范圍——變量a
  • 訪問outer函數(shù)的變量——變量b
  • 訪問可能定義的任何全局變量 

進一步了解閉包

為了深入了解閉包,讓我們通過添加三行代碼來擴充示例:

function outer() {
var b = 10;
var c = 100;
   function inner() {
        
         var a = 20; 
         console.log("a= " + a + " b= " + b);
         a++;
         b++;
    }
   return inner;
}
var X = outer();  // outer() 第一次被調用
var Y = outer();  // outer() 第二次被調用
//outer()函數(shù)執(zhí)行完畢
X(); // X() 第一次調用
X(); // X() 第二次調用
X(); // X() 第三次調用
Y(); // Y() 第一次調用

demodownload

輸出

a=20 b=10
a=20 b=11
a=20 b=12
a=20 b=10

 

讓我們一步一步地檢查這段代碼,看看到底發(fā)生了什么,看看閉包的實際效果!

var X = outer();  // outer()第一次調用

outer()第一次調用,執(zhí)行以下步驟:

  • 創(chuàng)建變量b,并設置為10;
    創(chuàng)建變量c,并設置為100。
    我們在引用中調用b(第一次)c(第一次)。
  • 此時返回inner函數(shù)并賦給X,變量b作為閉包以b=10包含在inner函數(shù)作用域鏈中,因為inner使用了變量b
  • outer函數(shù)完成執(zhí)行,其所有變量不再存在。變量c不再存在,盡管變量b作為閉包存在于inner中。

 

var Y= outer();  // outer()第二次調用
  • 重新創(chuàng)建變量b,并設置為10;
    重新創(chuàng)建變量c,并設置為100;
    請注意,即使變量之前執(zhí)行過一次并且不再存在,一旦函數(shù)完成執(zhí)行,它們就會被創(chuàng)建為全新的變量。
    我們調用b(第二次)和c(第二次)作為我們的引用。
  • 此時返回inner函數(shù)并賦給Y,變量b作為閉包以b(第二次)=10包含在inner函數(shù)作用域鏈中,因為inner使用了變量b。
  • outer函數(shù)完成執(zhí)行,其所有變量不再存在。
    變量c(第二次)不再存在,盡管變量b(第二次)作為閉包存在于inner中。

現(xiàn)在讓我們看看執(zhí)行以下代碼行時會發(fā)生什么:

X(); // X() 第一次調用
X(); // X() 第二次調用
X(); // X() 第三次調用
Y(); // Y() 第一次調用

X()第一次調用時,

  • 變量a被創(chuàng)建,并設置為20。
  • a的值=20, b的值來自閉包值,b(第一次), 所以b=10
  • 變量ab都遞增1。
  • X()完成執(zhí)行,其所有內部變量(變量a)不再存在。
    但是,b(第一次)被保存為閉包,所以b(第一次)繼續(xù)存在。

X()第二次調用時,

  • 變量a被重新創(chuàng)建,并設置為20。
  • 變量a任何先前的值不再存在,因為它在X()第一次完成執(zhí)行時不再存在。
  • a的值=20;b的值取自閉包值b(第一次),還要注意,我們在上一次執(zhí)行中增加了b的值,所以b=11。
  • 變量ab再次遞增1。
  • X()完成執(zhí)行并且它的所有內部變量(變量 a ) 不再存在。
    但是,b(第一次)隨著閉包繼續(xù)存在而被保留。

X()第三次調用時,

  • 變量a被重新創(chuàng)建,并設置為20;
    變量a任何先前的值不再存在,因為它在X()第二次完成執(zhí)行時不再存在。
  • b的值來自閉包值——b(第一次);
    還要注意我們在之前的執(zhí)行中為b的值增加了1, 所以b=12
  • 變量ab再次遞增1。
  • X()完成執(zhí)行,其所有內部變量 (變量a)不再存在。
    但是,b(第一次)隨著閉包繼續(xù)存在而被保留。

第一次調用 Y() 時,

  • 變量a被重新創(chuàng)建,并設置為20;
    a的值=20, b的值來自閉包值—— b(第二次),所以b=10。
  • 變量ab均遞增1。
  • Y()完成執(zhí)行,它的所有內部變量(變量a)不再存在。
    但是,b(第二次)被保存為閉包,所以b(第二次)繼續(xù)存在。

結束語

閉包是 JavaScript 中一開始難以掌握的微妙概念之一。但是一旦你理解了它們,你就會意識到事情并沒有那么復雜難懂。

相關文章

標簽: 閉包  
x
  • 站長推薦
/* 左側顯示文章內容目錄 */