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

贊助商

分類目錄

贊助商

最新文章

搜索

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

作者:admin    時(shí)間:2022-6-7 17:19:34    瀏覽:

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

什么是閉包?

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

閉包具有三個(gè)作用域鏈:

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

對(duì)于新手來(lái)說(shuō),這個(gè)定義似乎不好理解。不過(guò)沒(méi)關(guān)系,下面會(huì)通過(guò)簡(jiǎn)單的示例說(shuō)明,新手也可很快理解它。

真正的閉包是什么?

讓我們看一個(gè) JavaScript 中的簡(jiǎn)單閉包示例:

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

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

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

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

現(xiàn)在讓我們調(diào)用outer()函數(shù),并將結(jié)果存儲(chǔ)在一個(gè)變量X中。然后我們?cè)俅握{(diào)用outer()函數(shù)并將其存儲(chǔ)在變量Y中。

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

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

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

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

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

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

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

因此,當(dāng)outer()第二次調(diào)用時(shí):

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

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

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

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

這可以通過(guò)在 JavaScript 代碼中添加以下內(nèi)容來(lái)輕松驗(yàn)證:

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

由于變量XY是函數(shù),我們可以執(zhí)行它們。在 JavaScript 中,可以通過(guò)()在函數(shù)名稱后添加來(lái)執(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() 第一次調(diào)用
X(); // X() 第二次調(diào)用
X(); // X() 第三次調(diào)用
Y(); // Y() 第一次調(diào)用

demodownload

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

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

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

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

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

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

因此,JavaScript 知道a=20b=10,并且可以計(jì)算a+b

你可以通過(guò)在上面的示例中添加以下代碼行來(lái)驗(yàn)證這一點(diǎn):

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()

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

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

現(xiàn)在讓我們重新回顧一下我們?cè)陂_(kāi)始時(shí)看到的閉包的定義,看看它現(xiàn)在是否更有意義。

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

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

進(jìn)一步了解閉包

為了深入了解閉包,讓我們通過(guò)添加三行代碼來(lái)擴(kuò)充示例:

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() 第一次被調(diào)用
var Y = outer();  // outer() 第二次被調(diào)用
//outer()函數(shù)執(zhí)行完畢
X(); // X() 第一次調(diào)用
X(); // X() 第二次調(diào)用
X(); // X() 第三次調(diào)用
Y(); // Y() 第一次調(diào)用

demodownload

輸出

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

 

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

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

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

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

 

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

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

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

X()第一次調(diào)用時(shí),

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

X()第二次調(diào)用時(shí),

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

X()第三次調(diào)用時(shí),

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

第一次調(diào)用 Y() 時(shí),

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

結(jié)束語(yǔ)

閉包是 JavaScript 中一開(kāi)始難以掌握的微妙概念之一。但是一旦你理解了它們,你就會(huì)意識(shí)到事情并沒(méi)有那么復(fù)雜難懂。

相關(guān)文章

標(biāo)簽: 閉包  
x
  • 站長(zhǎng)推薦
/* 左側(cè)顯示文章內(nèi)容目錄 */