成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

淺談對(duì)JavaScript閉包的理解

missonce / 1368人閱讀

摘要:關(guān)于循環(huán)和閉包當(dāng)循環(huán)和閉包結(jié)合在一起時(shí),經(jīng)常會(huì)產(chǎn)生讓初學(xué)者覺得匪夷所思的問題。閉包是一把雙刃劍是比較難以理解和掌握的部分,它十分強(qiáng)大,卻也有很大的缺陷,如何使用它完全取決于你自己。

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

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

JavaScript查找標(biāo)識(shí)符的機(jī)制

JavaScript的作用域是詞法作用域

JavaScript的垃圾回收機(jī)制

先來(lái)說(shuō)說(shuō)函數(shù)表達(dá)式

什么是函數(shù)表達(dá)式? 如果function是聲明中的第一個(gè)詞,那么就是函數(shù)聲明,否則就是函數(shù)表達(dá)式
舉個(gè)例子:

var foo = function(){}; //匿名函數(shù)表達(dá)式

(function foo(){})() //函數(shù)表達(dá)式,因?yàn)閒unction不是聲明中的第一個(gè)詞,前面還有一個(gè)“(”

function foo(){} //函數(shù)聲明

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

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

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

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

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

函數(shù)聲明具有函數(shù)聲明提升,函數(shù)表達(dá)式不會(huì)被提升

函數(shù)表達(dá)式可以在表達(dá)式后跟個(gè)括號(hào)來(lái)立即執(zhí)行,函數(shù)聲明不行

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

這種模式的函數(shù),通常稱為IIFE(Immediately Invoked Function Expresstion)代表立即執(zhí)行函數(shù)表達(dá)式。
關(guān)于函數(shù)、變量聲明的提升這里就不再多說(shuō)了, 想了解的同學(xué)可以查閱一下相關(guān)資料

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

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

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

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

JavaScript的作用域

JavaScript的作用域就是詞法作用域而不是動(dòng)態(tài)作用域
詞法作用域最重要的特征是它的定義過程發(fā)生在代碼的書寫階段
動(dòng)態(tài)作用域的作用域鏈?zhǔn)腔?b>調(diào)用棧的 詞法作用域的作用域鏈?zhǔn)腔?b>代碼中的作用域嵌套

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

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

下面來(lái)說(shuō)閉包

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

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

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

我個(gè)人更傾向于后者對(duì)于閉包的定義,即閉包是一個(gè)引用。
下面來(lái)看一些代碼:

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

var bar = foo();
bar();       // 5
    

這段代碼里 foo執(zhí)行時(shí)會(huì)返回一個(gè)匿名函數(shù)表達(dá)式,這個(gè)函數(shù)能夠訪問foo()的作用域,并且引用能引用它,然后將這個(gè)匿名函數(shù)賦值給了變量bar,讓bar能引用這個(gè)匿名函數(shù)并且可以調(diào)用它。
這個(gè)例子,匿名函數(shù)在自己定義的詞法作用域以外的地方成功執(zhí)行。
這正是閉包強(qiáng)大的地方,比如通過閉包實(shí)現(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)建了一個(gè)模塊實(shí)例,函數(shù)返回的這個(gè)對(duì)象,實(shí)質(zhì)上可以看做是這個(gè)模塊的公告API,是不是有些像其它面向?qū)ο笳Z(yǔ)言中的class?

再來(lái)通過閉包實(shí)現(xiàn)一個(gè)單例模式:

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

這個(gè)例子通過IIFE創(chuàng)建了一個(gè)單例對(duì)象,函數(shù)里返回的對(duì)象字面量是這個(gè)單例模式的公共接口。
通過閉包實(shí)現(xiàn)模塊模式,可以做到很多強(qiáng)大的事情,模塊模式能成功實(shí)現(xiàn),最關(guān)鍵的是返回的API還能繼續(xù)引用定義時(shí)所在的作用域,從而進(jìn)行一些操作,也就是說(shuō),作用域并沒有因?yàn)楹瘮?shù)執(zhí)行后被銷毀,也就是沒有被內(nèi)存回收,之所以沒有被回收是因?yàn)殚]包的存在和JavaScript的垃圾回收機(jī)制。

JavaScript的垃圾回收機(jī)制

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

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

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

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

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

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

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

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

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

當(dāng)我們?cè)谕獠空{(diào)用函數(shù)時(shí),函數(shù)沿著它的作用域鏈開始搜索所需要的變量,前面說(shuō)過,JavaScript的作用域鏈?zhǔn)腔诙x時(shí)的作用域嵌套,所以當(dāng)我們調(diào)用某個(gè)函數(shù)比如 result[0] 它就會(huì)首先在自己的作用域里通過RSH搜索 i ,顯然 i 不存在這個(gè)作用域中,于是它又沿著作用域鏈向上一級(jí)作用域中搜索 i ,然后找到了 i ,但是此時(shí)createFunction函數(shù)已經(jīng)執(zhí)行,循環(huán)也已經(jīng)執(zhí)行完畢了, i 的值為10,所以獲取到的i,值就為10,同理,其他的函數(shù)執(zhí)行時(shí),查找的i 也會(huì)是10, 所以每個(gè)函數(shù)執(zhí)行結(jié)果都是輸出10。
關(guān)鍵所在就是盡管循環(huán)中的十個(gè)函數(shù)是在各自的迭代中分別定義的,但是它們都處于一個(gè)共享的上一級(jí)作用域中,所以它們獲取到的都是一個(gè) i

所以解決此類問題的關(guān)鍵就是讓函數(shù)查找i時(shí),不找到createFunction的變量對(duì)象那一級(jí) ,因?yàn)橐坏┫蛏纤阉鞯絚reateFunction那里,得到的就是10。所以我們可以通過一些方法在中間來(lái)截?cái)啾驹撍阉鞯絚reateFunction變量對(duì)象的一次查找。

首先我們可以這樣:

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

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

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這個(gè)塊級(jí)作用域里定義了一個(gè)變量 i ,這個(gè) i 與上級(jí)的 i 不會(huì)互相影響,因?yàn)樗鼈兇嬖诟髯缘淖饔糜蚶铮?同時(shí)我們將該次迭代時(shí)的 i 值賦給了 a這個(gè)塊級(jí)作用域里的 i ,即a中的 i 保存了當(dāng)次迭代的 i ,result[i]在外部執(zhí)行時(shí),是這樣的調(diào)用鏈result i -> a在a中就能找到需要的變量,不需要再向上搜索,也不會(huì)查找到值為10的 i ,所以調(diào)用哪個(gè)result[i]函數(shù),就會(huì)輸出哪個(gè) i 。

ES6 中我們還可以使用 let 來(lái)解決此類問題

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會(huì)創(chuàng)建一個(gè)塊級(jí)作用域,并在這個(gè)作用域中聲明一個(gè)變量。所以我們相當(dāng)于在result[i]上套了一層塊級(jí)作用域

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)用時(shí)去查找到最上級(jí)的那個(gè) i 。

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

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

這樣在for頭部使用let聲明, 每次迭代都會(huì)進(jìn)行聲明,隨后每次迭代都會(huì)使用上一個(gè)迭代結(jié)束時(shí)的值來(lái)初始化這個(gè)變量。

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

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

以上皆為個(gè)人觀點(diǎn) 如若有誤 還望指正

參考書籍

《JavaScript高級(jí)程序設(shè)計(jì)》

《你不知道的JavaScript 上卷》

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/80363.html

相關(guān)文章

  • 談對(duì)CSRF認(rèn)識(shí),以及一些應(yīng)對(duì)措施

    摘要:了解了攻擊者利用的一些原理,就對(duì)應(yīng)的可以找到一些對(duì)應(yīng)措施在服務(wù)端驗(yàn)證的字段。因此,從某些方面來(lái)說(shuō),是相對(duì)安全的。個(gè)人覺得相對(duì)安全的做法就是既驗(yàn)證,同時(shí)也校驗(yàn)。整個(gè)過程雖然比較難,但這讓自己對(duì)于有了更深刻的認(rèn)識(shí)。 CSRF CSRF(Cross Site Request Forgery, 跨站域請(qǐng)求偽造)的定義,相信大家都不陌生。它是指攻擊者通過誘導(dǎo)用戶,打開已精心設(shè)計(jì)好的頁(yè)面后,發(fā)送請(qǐng)...

    isaced 評(píng)論0 收藏0
  • 淺談JavaScript閉包

    摘要:但是函數(shù)返回了內(nèi)部函數(shù),內(nèi)部函數(shù)會(huì)隨時(shí)訪問變量所以垃圾回收機(jī)制是不會(huì)回收函數(shù)的內(nèi)部作用域的,這就是閉包的含義。也就是函數(shù)在定義的詞法作用域以外的地方被調(diào)用,閉包使得函數(shù)可以繼續(xù)訪問定義時(shí)的詞法作用域。   初學(xué)JavaScript閉包時(shí),閉包這個(gè)概念在我眼里及其的神秘,也不知道這個(gè)東西在講什么,尤其某些地方的閉包概念定義的非常抽象,屬于那種本來(lái)你可能明白這個(gè)概念,看了反而又把你給繞糊涂...

    hsluoyz 評(píng)論0 收藏0
  • 淺談Javascript閉包和匿名函數(shù)【1】

    摘要:我們可以用普通函數(shù)內(nèi)部嵌套匿名函數(shù),形成一個(gè)閉包來(lái)使變量駐留在內(nèi)存中。局部變量閉包為什么要將賦值給變量呢這里我們就要談到匿名函數(shù)調(diào)用問題匿名函數(shù)如何調(diào)用還是上面的例子會(huì)將整個(gè)函數(shù)體打印出來(lái)這樣才調(diào)用了函數(shù)內(nèi)部的匿名函數(shù)看到這里。 閉包含義: 閉包是指有權(quán)訪問另一個(gè)函數(shù)作用域中的變量的函數(shù),創(chuàng)建閉包的常見的方式,就是在一個(gè)函數(shù)內(nèi)部創(chuàng)建另一個(gè)函數(shù),通過另一個(gè)函數(shù)訪問這個(gè)函數(shù)的局部變量。 這...

    cyqian 評(píng)論0 收藏0
  • 淺談JavaScript閉包

    摘要:在內(nèi)部,理所當(dāng)然能訪問到局部變量,但當(dāng)作為的返回值賦給外的全局變量時(shí),神奇的事情發(fā)生了在全局作用域中訪問到了,這就是閉包。而閉包最神奇的地方就是能在一個(gè)函數(shù)外訪問函數(shù)中的局部變量,把這些變量用閉包的形式放在函數(shù)中便能避免污染。 一、閉包是什么? 《JavaScript高級(jí)程序設(shè)計(jì)》中寫道:閉包是指有權(quán)訪問另一個(gè)函數(shù)作用域中的變量的函數(shù),如果用下定義的觀點(diǎn)看,這句話就是說(shuō)閉包是函數(shù),我...

    Riddler 評(píng)論0 收藏0
  • 淺談Javascript閉包中作用域及內(nèi)存泄漏問題

    摘要:將作用域賦值給變量這里的作用域是,而不是將作用域賦值給一個(gè)變量閉包返回瀏覽器中內(nèi)存泄漏問題大家都知道,閉包會(huì)使變量駐留在內(nèi)存中,這也就導(dǎo)致了內(nèi)存泄漏。 上一章我們講了匿名函數(shù)和閉包,這次我們來(lái)談?wù)勯]包中作用域this的問題。 大家都知道,this對(duì)象是在運(yùn)行時(shí)基于函數(shù)的執(zhí)行環(huán)境綁定的,如果this在全局就是[object window],如果在對(duì)象內(nèi)部就是指向這個(gè)對(duì)象,而閉包卻是在運(yùn)行...

    source 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<