真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

JavaScript閉包知識點整理

這篇文章主要講解了“JavaScript閉包知識點整理”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“JavaScript閉包知識點整理”吧!

創(chuàng)新互聯(lián)建站服務(wù)項目包括達日網(wǎng)站建設(shè)、達日網(wǎng)站制作、達日網(wǎng)頁制作以及達日網(wǎng)絡(luò)營銷策劃等。多年來,我們專注于互聯(lián)網(wǎng)行業(yè),利用自身積累的技術(shù)優(yōu)勢、行業(yè)經(jīng)驗、深度合作伙伴關(guān)系等,向廣大中小型企業(yè)、政府機構(gòu)等提供互聯(lián)網(wǎng)行業(yè)的解決方案,達日網(wǎng)站推廣取得了明顯的社會效益與經(jīng)濟效益。目前,我們服務(wù)的客戶以成都為中心已經(jīng)輻射到達日省份的部分城市,未來相信會繼續(xù)擴大服務(wù)區(qū)域并繼續(xù)獲得客戶的支持與信任!

在談閉包之前,我們首先要了解幾個概念:

  • 什么是函數(shù)表達式? 與函數(shù)聲明有何不同?

  • JavaScript查找標(biāo)識符的機制

  • JavaScript的作用域是詞法作用域

  • JavaScript的垃圾回收機制

先來說說函數(shù)表達式

什么是函數(shù)表達式? 如果function是聲明中的***個詞,那么就是函數(shù)聲明,否則就是函數(shù)表達式。

舉個例子:

var foo = function(){}; //匿名函數(shù)表達式  (function foo(){})() //函數(shù)表達式,因為function不是聲明中的***個詞,前面還有一個“(”  function foo(){} //函數(shù)聲明

函數(shù)表達式也分匿名函數(shù)表達式和具名函數(shù)表達式:

var foo = function(){} //匿名函數(shù)表達式  var foo = function bar(){} //具名函數(shù)表達式

具名函數(shù)表達式要注意一點:上例中的bar標(biāo)識符 只在當(dāng)前的函數(shù)作用域中存在,在全局作用域中是不存在的。

函數(shù)聲明與函數(shù)表達式的重要區(qū)別有:

  • 函數(shù)聲明具有函數(shù)聲明提升,函數(shù)表達式不會被提升

  • 函數(shù)表達式可以在表達式后跟個括號來立即執(zhí)行,函數(shù)聲明不行

(function (){})() //匿名函數(shù)表達式,且立即執(zhí)行

這種模式的函數(shù),通常稱為IIFE(Immediately Invoked Function Expresstion)代表立即執(zhí)行函數(shù)表達式。

關(guān)于函數(shù)、變量聲明的提升這里就不再多說了, 想了解的同學(xué)可以查閱一下相關(guān)資料

關(guān)于JavaScript執(zhí)行函數(shù)時查找標(biāo)識符的機制

不了解作用域鏈及變量對象的同學(xué)可以先查閱相關(guān)資料后再來看。

作用域鏈本質(zhì)上是一個由指向變量對象的指針列表,它只引用但不實際包含變量對象,變量,函數(shù)等等都存在各自作用域的變量對象中,通過訪問變量對象來訪問它們。

只有在函數(shù)調(diào)用的時候,才會創(chuàng)建執(zhí)行環(huán)境和作用域鏈,同時每個環(huán)境都只能逐級向上搜索作用域鏈,來查詢變量和函數(shù)名等標(biāo)識符。

JavaScript的作用域

JavaScript的作用域就是詞法作用域而不是動態(tài)作用域

詞法作用域最重要的特征是它的定義過程發(fā)生在代碼的書寫階段

動態(tài)作用域的作用域鏈是基于調(diào)用棧的 詞法作用域的作用域鏈是基于代碼中的作用域嵌套

function foo(){     console.log(num) }     function bar(){     var num = 2;     foo(); // 1 }      var num = 1; bar();

bar函數(shù)執(zhí)行時,會執(zhí)行foo函數(shù),因為JavaScript是詞法作用域,所以函數(shù)執(zhí)行時,會沿著定義時的作用域鏈查找變量,而不是執(zhí)行時,foo函數(shù)定義在全局中,所以查找到了全局的num,輸出了1而不是2。

下面來說閉包

關(guān)于什么是閉包,其實有很多種說法,這取決于各自的理解,最主要的有兩種:

  • Nicolas C.Zakas:閉包是指有權(quán)訪問另一個函數(shù)作用域中的變量的函數(shù)

  • KYLE SIMPSON:當(dāng)函數(shù)可以記住并訪問所在的詞法作用域時,就產(chǎn)生了閉包,這個函數(shù)持有對該詞法作用域的引用,這個引用就叫做閉包

我個人更傾向于后者對于閉包的定義,即閉包是一個引用。下面來看一些代碼:

function foo() {     var a = 5;     return function() {     console.log(a);     }  }  var bar = foo(); bar();       // 5

這段代碼里  foo執(zhí)行時會返回一個匿名函數(shù)表達式,這個函數(shù)能夠訪問foo()的作用域,并且引用能引用它,然后將這個匿名函數(shù)賦值給了變量bar,讓bar能引用這個匿名函數(shù)并且可以調(diào)用它。

這個例子,匿名函數(shù)在自己定義的詞法作用域以外的地方成功執(zhí)行。

這正是閉包強大的地方,比如通過閉包實現(xiàn)模塊模式:

function aModule() {      var sometext = "module";          function doSomething() {         console.log(sometext);     }          return {         doSomething: doSomething         }; }  var obj = aModule(); obj.doSomething()   //module

我們通過調(diào)用aModule函數(shù)創(chuàng)建了一個模塊實例,函數(shù)返回的這個對象,實質(zhì)上可以看做是這個模塊的公告API,是不是有些像其它面向?qū)ο笳Z言中的class?

再來通過閉包實現(xiàn)一個單例模式:

var application = function() {          var components = [];     /*     一些初始化操作     */     return {              //公共API         getComponentCount: function() {         return components.length;         },         registerComponent: function(component) {         components.push(component);         }     }; }();

這個例子通過IIFE創(chuàng)建了一個單例對象,函數(shù)里返回的對象字面量是這個單例模式的公共接口。

通過閉包實現(xiàn)模塊模式,可以做到很多強大的事情,模塊模式能成功實現(xiàn),最關(guān)鍵的是返回的API還能繼續(xù)引用定義時所在的作用域,從而進行一些操作,也就是說,作用域并沒有因為函數(shù)執(zhí)行后被銷毀,也就是沒有被內(nèi)存回收,之所以沒有被回收是因為閉包的存在和JavaScript的垃圾回收機制。

JavaScript的垃圾回收機制

JavaScript最常用的垃圾收集方式是標(biāo)記清除,垃圾收集器會給存儲在內(nèi)存中的所有變量都加上標(biāo)記,然后去除環(huán)境中的變量,以及被環(huán)境中的變量引用的變量的標(biāo)記,說明這些變量還有作用,暫時不能被刪除,然后在此之后被加上標(biāo)記的變量就是要刪除的變量了,等待垃圾收集器對他們完成清除工作。

對函數(shù)來說,函數(shù)執(zhí)行完畢后,會自動釋放掉里面的變量,可是如果函數(shù)內(nèi)部存在閉包,它們就不會被刪除,因為這個函數(shù)還在被內(nèi)部的函數(shù)所引用,所以他不會被加上標(biāo)記,不會被清除,而是會一直存在內(nèi)存中得不到釋放!除非使用閉包的那個內(nèi)部函數(shù)被銷毀,外部函數(shù)才能得到釋放

所以,雖然閉包強大,但是我們不能濫用它,且在沒有必要的情況下盡量不要創(chuàng)建閉包,不然將會有大量的變量對象得不到釋放,過度占用內(nèi)存。

關(guān)于循環(huán)和閉包

當(dāng)循環(huán)和閉包結(jié)合在一起時,經(jīng)常會產(chǎn)生讓初學(xué)者覺得匪夷所思的問題。來看一段Nicolas C.Zakas  在《JavaScript高級程序設(shè)計》中的代碼:

function createFunction() {     var result = [];     for (var i = 0; i < 10; i++) {         result[i] = function() {             return i;         };     }     return result; }

這個函數(shù)執(zhí)行后,會創(chuàng)建一個由十個函數(shù)組成的數(shù)組,并且產(chǎn)生十個互不相干的函數(shù)作用域,表面上看調(diào)用第幾個函數(shù)就會輸出幾,但是結(jié)果并不是這樣

var result = createFunction(); result[0]();  // 10 result[9]();  // 10

產(chǎn)生這種奇怪的現(xiàn)象的原因就是之前說的,createFunction的變量對象因為閉包的存在沒有被釋放,注意閉包保存的是整個變量對象,而不是只保存只被引用的變量,在createFunction執(zhí)行后,創(chuàng)建了十個函數(shù),同時變量  i 沒有被釋放,依然保存在內(nèi)存中,所以此時它的值保留為停止循環(huán)后的10。

當(dāng)我們在外部調(diào)用函數(shù)時,函數(shù)沿著它的作用域鏈開始搜索所需要的變量,前面說過,JavaScript的作用域鏈是基于定義時的作用域嵌套,所以當(dāng)我們調(diào)用某個函數(shù)比如  result[0] 它就會首先在自己的作用域里通過RSH搜索 i ,顯然 i 不存在這個作用域中,于是它又沿著作用域鏈向上一級作用域中搜索 i ,然后找到了 i  ,但是此時createFunction函數(shù)已經(jīng)執(zhí)行,循環(huán)也已經(jīng)執(zhí)行完畢了, i 的值為10,所以獲取到的i,值就為10,同理,其他的函數(shù)執(zhí)行時,查找的i  也會是10, 所以每個函數(shù)執(zhí)行結(jié)果都是輸出10。

關(guān)鍵所在就是盡管循環(huán)中的十個函數(shù)是在各自的迭代中分別定義的,但是它們都處于一個共享的上一級作用域中,所以它們獲取到的都是一個 i

所以解決此類問題的關(guān)鍵就是讓函數(shù)查找i時,不找到createFunction的變量對象那一級  ,因為一旦向上搜索到createFunction那里,得到的就是10。所以我們可以通過一些方法在中間來截斷本該搜索到createFunction變量對象的一次查找。

首先我們可以這樣:

function createFunction() {     var result = [];     for (var i = 0; i < 10; i++) {     (function (){         result[i] = function() {             return i;         };})();     }     return result; }

我們通過定義一個立即執(zhí)行函數(shù)表達式,在result[i]函數(shù)上一級創(chuàng)建了一個塊級作用域,如果我們把這個塊級作用域叫做a,那么它查找i時是這樣一條鏈  result[i]->a->createFunction,之所以還會查找到createFunction中,是因為a中沒有i這個變量,所以我們需要做些什么,讓它搜索到a時就停下

function createFunctions() {     var result = new Array();     for (var i = 0; i < 10; i++) {         (function(i){         result[i] = function() {             return i;         };})(i);         }          return result; }

現(xiàn)在a這個塊級作用域里定義了一個變量 i ,這個 i 與上級的 i 不會互相影響,因為它們存在各自的作用域里, 同時我們將該次迭代時的 i 值賦給了  a這個塊級作用域里的 i ,即a中的 i 保存了當(dāng)次迭代的 i ,result[i]在外部執(zhí)行時,是這樣的調(diào)用鏈result i ->  a在a中就能找到需要的變量,不需要再向上搜索,也不會查找到值為10的 i ,所以調(diào)用哪個result[i]函數(shù),就會輸出哪個 i 。

在 ES6 中我們還可以使用 let 來解決此類問題

function createFunction() {     var result = [];     for (var i = 0; i < 10; i++) {         let j = i;         result[i] = function() {             return j;         };     }     return result; } //輸出一下 console.log(createFunction()[2]());  //2

let會創(chuàng)建一個塊級作用域,并在這個作用域中聲明一個變量。所以我們相當(dāng)于在result[i]上套了一層塊級作用域

function createFunction() {     var result = [];     for (var i = 0; i < 10; i++) {         //塊的開始         let j = i;         result[i] = function() {             return j;         };         //塊的結(jié)束     }     return result; }

這種方式解決此類問題,與前面沒有多大分別,總之就是為了不讓函數(shù)調(diào)用時去查找到最上級的那個 i 。

其實,如果在for循環(huán)頭部來進行l(wèi)et聲明還會有一個有趣的行為:

function createFunction() {     var result = [];     for (let i = 0; i < 10; i++) {    //每次迭代,都會聲明一次i,總共聲明10次         result[i] = function() {             return i;         };     }     return result; } console.log(createFunction()[2]());  //2

這樣在for頭部使用let聲明, 每次迭代都會進行聲明,隨后每次迭代都會使用上一個迭代結(jié)束時的值來初始化這個變量。

事實上當(dāng)函數(shù)當(dāng)做值類型并到處傳遞時, 基本都會使用閉包,如定時器,跨窗口通信,事件監(jiān)聽,ajax等等 基本只要使用了回調(diào)函數(shù),  實際上就是在使用閉包。

閉包是一把雙刃劍 是JavaScript比較難以理解和掌握的部分, 它十分強大,卻也有很大的缺陷,如何使用它完全取決于你自己。

感謝各位的閱讀,以上就是“JavaScript閉包知識點整理”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對JavaScript閉包知識點整理這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是創(chuàng)新互聯(lián),小編將為大家推送更多相關(guān)知識點的文章,歡迎關(guān)注!


新聞名稱:JavaScript閉包知識點整理
網(wǎng)頁地址:http://weahome.cn/article/ipedos.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部