自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

重學(xué)JavaScript(函數(shù))閉包

開發(fā) 后端
JavaScript函數(shù)內(nèi)部可以讀取函數(shù)外部的變,但反過來,函數(shù)的外部通常則無法讀取函數(shù)內(nèi)部的變量。在實(shí)際應(yīng)用中,有時需要真正在函數(shù)外部訪問函數(shù)內(nèi)部的局部變量,此時最常用的方法就是使用閉包。

 前言
我們知道,作用域鏈查找標(biāo)識符的順序是從當(dāng)前作用域開始一級一級往上查找。因此,通過作用域鏈,JavaScript函數(shù)內(nèi)部可以讀取函數(shù)外部的變,但反過來,函數(shù)的外部通常則無法讀取函數(shù)內(nèi)部的變量。在實(shí)際應(yīng)用中,有時需要真正在函數(shù)外部訪問函數(shù)內(nèi)部的局部變量,此時最常用的方法就是使用閉包。

那么什么是閉包?所謂閉包,就是同時含有對函數(shù)對象以及作用域?qū)ο笠玫膶ο?。閉包主要是用來獲取作用域鏈或原型鏈上的變量或值。創(chuàng)建閉包最常見的方式是在一個函數(shù)中聲明內(nèi)部函數(shù)(也稱嵌套函數(shù)),并返回內(nèi)部函數(shù)。此時在函數(shù)外部就可以通過調(diào)用函數(shù)得到內(nèi)部函數(shù)。雖然按照閉包的概念,所有訪問了外部變量的JavaScript函數(shù)都是閉包。但我們平常絕大部分時候所謂的閉包其實(shí)指的就是內(nèi)部函數(shù)閉包。
閉包可以將一些數(shù)據(jù)封裝私有屬性以確保這些變量的安全訪問,這個功能給應(yīng)用帶來了極大的好處。需要注意的是,閉包如果使用不當(dāng),也會帶來一些意想不到的問題。下面就通過幾個示例來演示一下閉包的創(chuàng)建、使用和可能存在的問題及其解決方法。
示例1: 創(chuàng)建閉包。

  1. <!DOCTYPE html> 
  2. <html> 
  3. <head> 
  4.  <title>閉包</title> 
  5. </head> 
  6. <body> 
  7. <script type="text/javascript"
  8.  function outer(argument) { 
  9.   var b=0; 
  10.   return function inner (){ 
  11.    b++; 
  12.    console.log("內(nèi)部的b:"+b); 
  13.   } 
  14.  } 
  15.  var func =  outer();//1 通過外部變量引用函數(shù)返回的內(nèi)部函數(shù) 
  16.  console.log(func);//2 輸出內(nèi)部函數(shù)定義代碼 
  17.  func();//3 通過閉包訪問局部變量b,此時b=1; 
  18.  console.log("外部函數(shù)中b:"+b); //4 出錯,報(bào)引用錯誤。 
  19. </script> 
  20. </body> 
  21. </html> 

上述代碼在外部函數(shù)outer中聲明內(nèi)部函數(shù)inner,并返回內(nèi)部函數(shù),同時在outer函數(shù)外面,變量func引用了outer函數(shù)返回的內(nèi)部函數(shù),所以內(nèi)部函數(shù)inner是一個閉包。該閉包訪問了外部函數(shù)的局部變量b。1處代碼通過調(diào)用外部函數(shù)返回內(nèi)部函數(shù)并賦給外部變量func,使func變量引用內(nèi)部函數(shù),所以2處代碼將輸出inner函數(shù)的整個定義代碼。3處代碼通過對外部變量func添加一對小括號后調(diào)用內(nèi)部函數(shù)inner,從而達(dá)到在函數(shù)外部訪問局部變量b的目的。執(zhí)行4處的代碼時將報(bào)ReferenceError錯誤,因?yàn)閎是局部變量,不能在函數(shù)外部直接訪問局部變量。
我們知道函數(shù)執(zhí)行完畢時,運(yùn)行期上下文會被銷毀,與之關(guān)聯(lián)的活動對象也會隨之銷毀,因此離開函數(shù)后,屬于活動對象的局部變量將不能被訪問。但是為什么上述示例中的outer函數(shù)執(zhí)行完后,它的局部變量還能被內(nèi)部函數(shù)訪問呢?這個問題我們可以用作用域鏈來解釋。
當(dāng)執(zhí)行1處代碼調(diào)用outer函數(shù)時,JavaScript引擎會創(chuàng)建outer函數(shù)執(zhí)行上下文的作用域鏈,這個作用域鏈包含了outer函數(shù)執(zhí)行時的活動對象,同時JavaScript引擎也會創(chuàng)建一個閉包,而閉包因?yàn)樾枰L問outer函數(shù)的局部變量,因而其作用鏈也會引用outer的活動對象。這樣,當(dāng)outer函數(shù)執(zhí)行完后,它的作用域?qū)ο笠驗(yàn)橛虚]包的引用而依然存在,固而可以提供給閉包訪問。
上述示例中的內(nèi)部函數(shù)雖然有名稱,但在調(diào)用是并沒有用到這個名稱,所以內(nèi)部函數(shù)的名稱可以缺省,即可以將內(nèi)部函數(shù)修改為匿名函數(shù),從而簡化代碼。
示例2: 經(jīng)典閉包問題

  1. <!DOCTYPE html> 
  2. <html> 
  3. <head> 
  4. <title>經(jīng)典閉包問題</title> 
  5. <script type="text/javascript"
  6.  window.onload=function () { 
  7.   var abtn = document.getElementsByTagName("button"); 
  8.   for (var i = 0; i<abtn.length; i++) { 
  9.    abtn[i].onclick=function(){ 
  10.     alert("按鈕"+(i+1)); 
  11.    } 
  12.   } 
  13.  } 
  14. </script> 
  15. </head> 
  16. <body> 
  17. <button>按鈕1</button> 
  18. <button>按鈕2</button> 
  19. <button>按鈕3</button> 
  20. </body> 
  21. </html> 

該示例期望實(shí)現(xiàn)的功能是,單擊每個按鈕時,在彈出的警告對話框中顯示相應(yīng)的標(biāo)簽內(nèi)容,即單擊3個按鈕時將分別顯示“按鈕1”、“按鈕2”、“按鈕3”。
上述示例頁面加載完后觸發(fā)窗口加載事件,從而執(zhí)行外層匿名函數(shù),外層匿名函數(shù)執(zhí)行完循環(huán)語句后使活動對象中的局部變量i的值修改為3。外層匿名函數(shù)執(zhí)行完后撤銷,但由于其活動對象中的abtn和i變量被內(nèi)層匿名函數(shù)引用,因而外層匿名函數(shù)的活動對象仍然存在堆中供內(nèi)層匿名函數(shù)訪問。每執(zhí)行一次循環(huán)都將創(chuàng)建一個閉包,這些閉包都引用了外層匿名函數(shù)的活動對象,因而訪問變量i時都得到3,這樣最后的結(jié)果是單擊每個按鈕,在警告對話框中顯示的文字都是“按鈕4” (i+1=3+1),與期望的功能不一致。造成這個問題的原因是,每個閉包都引用一個變量,如果我們使不同的閉包引用不同的變量,就可以實(shí)現(xiàn)輸出的結(jié)果不一樣。這個需求可使用多種方法實(shí)現(xiàn),在此介紹使用立即調(diào)用函數(shù)表達(dá)式(IIFE)和ES6中的let創(chuàng)建塊即變量的方法。
IIFE指的是:在定義函數(shù)的時候直接執(zhí)行,即此時函數(shù)定義變成了一個函數(shù)調(diào)用的語句。要讓一個函數(shù)定義語句變成函數(shù)調(diào)用語句,就需要將定義語句變?yōu)橐粋€函數(shù)表達(dá)式,然后在該表達(dá)式后面再加一對圓括號()即可。將函數(shù)定義語句變?yōu)橐粋€函數(shù)表達(dá)式的最常用方法就是將整個定義語句放在一對圓括號中。
1、IIFE中的函數(shù)為一個匿名函數(shù)

  1. (function(name){ 
  2.  console.log("hello,"+name); 
  3. })("maomin"); 

JS引擎執(zhí)行上述代碼時,會調(diào)用匿名,同時將后面圓括號中的參數(shù)maomin傳給name虛參,結(jié)果得到:"hello,maomin"。
2、IIFE中的函數(shù)為一個有名函數(shù)

  1. (function func (name) { 
  2.  console.log("I am"+name); 
  3. })("maomin"

上述代碼跟匿名函數(shù)完全一樣。

示例3: 使用立即調(diào)用函數(shù)表達(dá)式解決經(jīng)典閉包問題

  1. <!DOCTYPE html> 
  2. <html> 
  3. <head> 
  4.  <title>使用立即調(diào)用表達(dá)式解決經(jīng)典閉包問題</title> 
  5.  <script type="text/javascript"
  6.  window.onload=function () { 
  7.   var abtn = document.getElementsByTagName("button"); 
  8.   for (var i = 0; i<abtn.length; i++) { 
  9.    (function(num){ 
  10.     abtn[num].onclick=function(){ 
  11.         alert("按鈕"+(num+1)); 
  12.        } 
  13.    })(i) 
  14.   } 
  15.  } 
  16. </script> 
  17. </head> 
  18. <body> 
  19. <button>按鈕1</button> 
  20. <button>按鈕2</button> 
  21. <button>按鈕3</button> 
  22. </body> 
  23. </html> 

上述代碼中第二個匿名函數(shù)為IIFE,每次調(diào)用該匿名函數(shù)時將生成一個對應(yīng)該函數(shù)的活動對象。該對象中包含可一個函數(shù)參數(shù),值為當(dāng)次循環(huán)的循環(huán)變量值。上述示例中,IIFE共執(zhí)行了3次,因而共生成了3個活動對象,活動對象中包含的參數(shù)值分別為0、1和2,依次對應(yīng)IIFE的3次執(zhí)行。
每次執(zhí)行IIFE時,將會產(chǎn)生一個閉包,該閉包會引用對應(yīng)按鈕索引順序執(zhí)行IIFE的活動對象,而閉包引用的活動對象中的參數(shù)值剛好等于按鈕的索引值,因而單擊3個按鈕將在彈出的警告框中分別顯示"按鈕1"、“按鈕2”、“按鈕3”。
示例4:使用ES6中的let關(guān)鍵字創(chuàng)建塊級變量解決經(jīng)典閉包問題

  1. <!DOCTYPE html> 
  2. <html> 
  3. <head> 
  4.  <title>使用ES6中的let關(guān)鍵字解決經(jīng)典閉包問題</title> 
  5.  <script type="text/javascript"
  6.  window.onload=function () { 
  7.   var abtn = document.getElementsByTagName("button"); 
  8.   for (let i = 0; i<abtn.length; i++) { 
  9.    abtn[i].onclick=function(){ 
  10.     alert("按鈕"+(i+1)); 
  11.    } 
  12.   } 
  13.  } 
  14. </script> 
  15. </head> 
  16. <body> 
  17. <button>按鈕1</button> 
  18. <button>按鈕2</button> 
  19. <button>按鈕3</button> 
  20. </body> 
  21. </html> 

上述代碼中循環(huán)變量使用let聲明,因而每次循環(huán)時,都會產(chǎn)生一個新的塊級變量,所以在頁面加載完,執(zhí)行外層匿名函數(shù)時產(chǎn)生的活動對象中包含了3個對應(yīng)循環(huán)變量的塊級變量,變量值分為0、1和2。每執(zhí)行一次循環(huán),將會產(chǎn)生一個閉包,該閉包中的變量i會引用外層匿名函數(shù)的活動對象對應(yīng)按鈕索引的塊級變量,因而單擊3個按鈕時將在彈出的警告對話框中分別顯示“按鈕1”、“按鈕2”、“按鈕3”。

責(zé)任編輯:姜華 來源: 前端歷劫之路
相關(guān)推薦

2021-02-21 16:21:19

JavaScript閉包前端

2011-05-25 14:48:33

Javascript閉包

2021-01-22 07:48:07

JavaScript 高階函數(shù)閉包

2017-09-14 13:55:57

JavaScript

2016-09-14 09:20:05

JavaScript閉包Web

2009-07-24 17:30:37

Javascript閉

2021-05-21 09:01:29

JavaScript 前端函數(shù)閉包

2012-11-29 10:09:23

Javascript閉包

2009-03-17 15:36:29

JavaScript循環(huán)事件

2010-06-23 10:24:42

Javascript閉

2016-09-18 20:53:16

JavaScript閉包前端

2017-05-22 16:08:30

前端開發(fā)javascript閉包

2021-01-13 11:25:12

JavaScript閉包函數(shù)

2011-03-02 12:33:00

JavaScript

2011-05-12 18:26:08

Javascript作用域

2011-05-30 14:41:09

Javascript閉

2024-01-22 09:51:32

Swift閉包表達(dá)式尾隨閉包

2021-12-06 07:15:48

Javascript作用域閉包

2023-07-11 08:46:38

閉包函數(shù)Rust

2023-02-07 07:47:52

Python裝飾器函數(shù)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號