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

資訊專欄INFORMATION COLUMN

搞懂閉包

masturbator / 3484人閱讀

摘要:此時(shí)閉包函數(shù)的作用域鏈得以保存,不會(huì)被垃圾回收機(jī)制所回收。執(zhí)行執(zhí)行完畢,返回總結(jié)閉包的原理,就是把閉包函數(shù)的作用域鏈保存了下來(lái)。

原文:搞懂閉包 | AlloyTeam
作者:TAT.yaoyao

閉包這個(gè)概念是前端工程師必須要深刻理解的,但是網(wǎng)上確實(shí)有一些文章會(huì)讓初學(xué)者覺(jué)得晦澀難懂,而且閉包的文章描述不一。

本文面向初級(jí)的程序員,聊一聊我對(duì)閉包的理解。當(dāng)然如果你看到閉包聯(lián)想不到作用域鏈垃圾回收也不妨看一眼。希望讀了它之后你不再對(duì)閉包蒙圈。

先體驗(yàn)一下閉包

這里有個(gè)需求,即寫一個(gè)計(jì)數(shù)器的函數(shù),每調(diào)用一次計(jì)數(shù)器返回值加一:

counter()    // 1
counter()    // 2
counter()    // 3
......

要想函數(shù)每次執(zhí)行的返回不一樣,怎么搞呢? 先簡(jiǎn)單的寫一下:

var index = 1;
function counter() {
    return index ++;
}

這樣做的確每次返回一個(gè)遞增的數(shù)。但是,它有以下三個(gè)問(wèn)題:

這個(gè)index放在全局,其他代碼可能會(huì)對(duì)他進(jìn)行修改

如果我需要同時(shí)用兩個(gè)計(jì)數(shù)器,但這種寫法只能滿足一個(gè)使用,另一個(gè)還想用的話就要再寫個(gè)counter2函數(shù),再定義一個(gè)index2的全局變量。

計(jì)數(shù)器是一個(gè)功能,我只希望我的代碼里有個(gè) counter函數(shù)就好,其他的最好不要出現(xiàn)。這是稍微有點(diǎn)代碼潔癖的都會(huì)覺(jué)得不爽的。

三個(gè)痛點(diǎn),讓閉包來(lái)一次性優(yōu)雅解決:

function counterCreator() {
    var index = 1;
    function counter() {
        return index ++;
    }
    return counter;
}

// test
var counterA = counterCreator();
var counterB = counterCreator();
counterA();        // 1
counterA();        // 2
counterB();        // 1
counterB();        // 2

我的counterCreator函數(shù)只是把上面的幾行代碼包起來(lái),然后返回了里面的 counter 函數(shù)而已。卻能同時(shí)解決這么多問(wèn)題,這就是閉包的魅力! 6不6???

鋪墊知識(shí)

鋪墊一些知識(shí)點(diǎn),不展開(kāi)講。

執(zhí)行上下文

函數(shù)每次執(zhí)行,都會(huì)生成一個(gè)會(huì)創(chuàng)建一個(gè)稱為執(zhí)行上下文的內(nèi)部對(duì)象(AO對(duì)象,可理解為函數(shù)作用域),這個(gè)AO對(duì)象會(huì)保存這個(gè)函數(shù)中所有的變量值和該函數(shù)內(nèi)部定義的函數(shù)的引用。函數(shù)每次執(zhí)行時(shí)對(duì)應(yīng)的執(zhí)行上下文都是獨(dú)一無(wú)二的,正常情況下函數(shù)執(zhí)行完畢執(zhí)行上下文就會(huì)被銷毀

作用域鏈

在函數(shù)定義的時(shí)候,他還獲得[[scope]]。這個(gè)是里面包含該函數(shù)的作用域鏈,初始值為引用著上一層作用域鏈里面所有的作用域,后面執(zhí)行的時(shí)候還會(huì)將AO對(duì)象添加進(jìn)去 。作用域鏈就是執(zhí)行上下文對(duì)象的集合,這個(gè)集合是鏈條狀的。

function a () {
    // (1)創(chuàng)建 a函數(shù)的AO對(duì)象:{ x: undfind,  b: function(){...}  , 作用域鏈上層:window的AO對(duì)象}
    var x = 1;
    function b () {
        // (3)創(chuàng)建 b函數(shù)的AO對(duì)象:{ y: undfind , 作用域鏈上層:a函數(shù)AO對(duì)象}
        var y = 2;
        // (4)b函數(shù)的AO對(duì)象:{ y: 3 , 作用域鏈上層:a函數(shù)AO對(duì)象}
        console.log(x, y);    // 在 b函數(shù)的AO對(duì)象中沒(méi)有找到x, 會(huì)到a函數(shù)AO對(duì)象中查找
    }
    //(2)此時(shí) a函數(shù)的AO對(duì)象:{ x: 1,  b: function(){...} , 作用域鏈上層:window的AO對(duì)象}
    b();
}
a();

正常情況函數(shù)每次執(zhí)行后AO對(duì)象都被銷毀,且每次執(zhí)行時(shí)都是生成新的AO對(duì)象。我們得出這個(gè)結(jié)論: 只要是這個(gè)函數(shù)每次調(diào)用的結(jié)果不一樣,那么這個(gè)函數(shù)內(nèi)部一定是使用了函數(shù)外部的變量。

垃圾回收

如何確定哪些內(nèi)存需要回收,哪些內(nèi)存不需要回收,這依賴于活對(duì)象這個(gè)概念。我們可以這樣假定:一個(gè)對(duì)象為活對(duì)象當(dāng)且僅當(dāng)它被一個(gè)根對(duì)象 或另一個(gè)活對(duì)象指向。根對(duì)象永遠(yuǎn)是活對(duì)象。

function a () {
    var x = 1;
    function b () {
        var y = 2;
        // b函數(shù)執(zhí)行完了,b函數(shù)AO被銷毀,y 被回收
    }
    b();
    //a 函數(shù)執(zhí)行完了,a函數(shù)AO被銷毀, x 和 b 都被回收
}
a();
// 這里是在全局下,window中的 a 直到頁(yè)面關(guān)閉才被回收。
分析閉包結(jié)構(gòu)
// 生成閉包的函數(shù)
function counterCreator() {

    // 被返回函數(shù)所依賴的變量
    var index = 1;

    // 被返回的函數(shù)
     function counter() {
        return index ++;
    }
    return counter;
}

// 被賦值為閉包函數(shù)
var counterA = counterCreator();

// 使用
counterA();

閉包的創(chuàng)造函數(shù)必定包含兩部分:

一些閉包函數(shù)執(zhí)行時(shí)依賴的變量,每次執(zhí)行閉包函數(shù)時(shí)都能訪問(wèn)和修改

返回的函數(shù),這個(gè)函數(shù)中必定使用到上面所說(shuō)的那些變量

// 被賦值的閉包函數(shù)
var counterA = counterCreator();
var counterB = counterCreator();

而上面這兩句代碼很重要,它其實(shí)是把閉包函數(shù)賦值給了一個(gè)變量,這個(gè)變量是一個(gè)活對(duì)象,這活對(duì)象引用了閉包函數(shù),閉包函數(shù)又引用了AO對(duì)象,所以這個(gè)時(shí)候AO對(duì)象也是一個(gè)活對(duì)象。此時(shí)閉包函數(shù)的作用域鏈得以保存,不會(huì)被垃圾回收機(jī)制所回收。

當(dāng)我們想重新創(chuàng)建一個(gè)新的計(jì)數(shù)器時(shí),只需要重新再調(diào)用一次 counterCreator, 他會(huì)新生成了一個(gè)新的執(zhí)行期上下文,所以counterBcounterA是互不干擾的。

counterCreator 執(zhí)行

counterCreator 執(zhí)行完畢,返回counter

總結(jié)

閉包的原理,就是把閉包函數(shù)的作用域鏈保存了下來(lái)。

使用閉包

帶你手寫一個(gè)簡(jiǎn)單的防抖函數(shù),趁熱打鐵。

第一步,先把閉包的架子搭起來(lái),因?yàn)槲覀円呀?jīng)分析了閉包生成函數(shù)內(nèi)部一定有的兩部分內(nèi)容。

function debunce(func, timeout) {
    // 閉包函數(shù)執(zhí)行時(shí)依賴的變量,每次執(zhí)行閉包函數(shù)時(shí)都能訪問(wèn)和修改
    return function() {
        // 這個(gè)函數(shù)最終會(huì)被賦值給一個(gè)變量
    }
}

第二步: 把閉包第一次執(zhí)行的情況寫出來(lái)

function debunce(func, timeout) {
    timeout = timeout || 300;
    return  function(...args)  {
        var _this = this;
        setTimeout(function () {
            func.apply(_this, args);
        }, timeout);
    }
}

第三步: 加上一些判斷條件。就像我們最開(kāi)始寫計(jì)數(shù)器的index一樣,不過(guò)這一次你不是把變量寫在全局下,而是寫在閉包生成器的內(nèi)部。

function debunce(func, timeout) {
    timeout = timeout || 300;
    var timer = null;    // 被閉包函數(shù)使用
    return  function(...args)  {
        var _this = this;
        clearTimeout(timer);    // 做一些邏輯讓每次執(zhí)行效果可不一致
        timer  = setTimeout(function () {
            func.apply(_this, args);
        }, timeout);
    }
}

// 測(cè)試:
function log(...args) {
    console.log("log: ", args);
}
var d_log = debunce(log, 1000);

d_log(1);    // 預(yù)期:不輸出
d_log(2);    // 預(yù)期:1s后輸出

setTimeout( function () {
    d_log(3);    // 預(yù)期:不輸出
    d_log(4);    // 預(yù)期:1s后輸出
}, 1500)
閉包運(yùn)用

閉包用到的真的是太多了,再舉幾個(gè)例子再來(lái)鞏固一下:

模塊化

例NodeJS模塊化原理:
NodeJS 會(huì)給每個(gè)文件包上這樣一層函數(shù),引入模塊使用require,導(dǎo)出使用exports,而那些文件中定義的變量也將留在這個(gè)閉包中,不會(huì)污染到其他地方。

(funciton(exports, require, module, __filename, __dirname) {
    /* 自己寫的代碼  */
})();
高階函數(shù)

一些使用閉包的經(jīng)典例子:

節(jié)流函數(shù)

柯里化(Currying)

組合(Composing)

bind的實(shí)現(xiàn)

最后,如果你對(duì)閉包有更好的理解或者我文章里寫的不好的地方,還請(qǐng)指教。

AlloyTeam 歡迎優(yōu)秀的小伙伴加入。
簡(jiǎn)歷投遞: [email protected]
詳情可點(diǎn)擊 騰訊AlloyTeam招募Web前端工程師(社招)

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

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

相關(guān)文章

  • 一篇文章搞懂閉包。

    摘要:如果非要用一句話定義閉包我更加認(rèn)同你不知道的作者的一句話當(dāng)函數(shù)可以記住并訪問(wèn)所在的詞法作用域時(shí),就產(chǎn)生了閉包。所以本文將會(huì)從閉包的應(yīng)用場(chǎng)景入手,來(lái)印證的這句話。總結(jié)閉包的應(yīng)用場(chǎng)景還有很多,可以說(shuō)是隨處可見(jiàn)了。 直接進(jìn)入主題,閉包是什么? 閉包是寫代碼過(guò)程產(chǎn)生的一種自然結(jié)果,而不是一種概念。 相比于一些概念性的解釋,更重要的是熟悉它的應(yīng)用場(chǎng)景、及常見(jiàn)寫法。 如果非要用一句話定義閉包:我...

    tinyq 評(píng)論0 收藏0
  • 搞懂JavaScript引擎運(yùn)行原理

    摘要:同步一次執(zhí)行一件事,同步引擎一次只執(zhí)行一行,是同步的。調(diào)用函數(shù)將其推入堆棧并從函數(shù)返回將其彈出堆棧。執(zhí)行上下文當(dāng)函數(shù)放入到調(diào)用堆棧時(shí)由創(chuàng)建的環(huán)境。執(zhí)行結(jié)果它會(huì)立即被推到回調(diào)隊(duì)列,但它仍然會(huì)等待調(diào)用堆棧為空才會(huì)執(zhí)行。 為了保證可讀性,本文采用意譯而非直譯。 想閱讀更多優(yōu)質(zhì)文章請(qǐng)猛戳GitHub博客,一年百來(lái)篇優(yōu)質(zhì)文章等著你! 一些名詞 JS引擎 — 一個(gè)讀取代碼并運(yùn)行的引擎,沒(méi)有單一的J...

    lastSeries 評(píng)論0 收藏0
  • 作用域閉包,你真的懂了嗎?

    摘要:曾幾何時(shí),閉包好像就是一個(gè)十分難以捉摸透的東西,看了很多文章,對(duì)閉包都各有說(shuō)法,以致讓我十分暈,什么內(nèi)部變量外部變量的,而且大多數(shù)都只描述一個(gè)過(guò)程,沒(méi)有給閉包的定義,最后,舉幾個(gè)例子,告訴你這就是閉包。 曾幾何時(shí),閉包好像就是一個(gè)十分難以捉摸透的東西,看了很多文章,對(duì)閉包都各有說(shuō)法,以致讓我十分暈,什么內(nèi)部變量、外部變量的,而且大多數(shù)都只描述一個(gè)過(guò)程,沒(méi)有給閉包的定義,最后,舉幾個(gè)例子...

    yangrd 評(píng)論0 收藏0
  • vue.js源碼 - 剖析observer,dep,watch三者關(guān)系 如何具體的實(shí)現(xiàn)數(shù)據(jù)雙向綁定

    摘要:雙向數(shù)據(jù)綁定的核心和基礎(chǔ)是其內(nèi)部真正參與數(shù)據(jù)雙向綁定流程的主要有和基于和發(fā)布者訂閱者模式,最終實(shí)現(xiàn)數(shù)據(jù)的雙向綁定。在這里把雙向數(shù)據(jù)綁定分為兩個(gè)流程收集依賴流程依賴收集會(huì)經(jīng)過(guò)以上流程,最終數(shù)組中存放列表,數(shù)組中存放列表。 Vue雙向數(shù)據(jù)綁定的核心和基礎(chǔ)api是Object.defineProperty,其內(nèi)部真正參與數(shù)據(jù)雙向綁定流程的主要有Obderver、Dep和Watcher,基于d...

    mo0n1andin 評(píng)論0 收藏0
  • js運(yùn)行機(jī)制及異步編程(一)

    摘要:引擎的運(yùn)行原理引擎也是程序,是屬于瀏覽器的一部分,由瀏覽器廠商自行開(kāi)發(fā)。為了提高運(yùn)行速度,現(xiàn)代瀏覽器一般采用即時(shí)編譯即字節(jié)碼只在運(yùn)行時(shí)編譯,用到哪一行就編譯哪一行,并且把編譯結(jié)果緩存這樣整個(gè)程序的運(yùn)行速度能得到顯著提升。 相信大家在面試的過(guò)程中經(jīng)常遇到查看執(zhí)行順序的問(wèn)題,如setTimeout,promise,async await等等,各種組合,是不是感覺(jué)頭都要暈掉了,其實(shí)這些問(wèn)題最...

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

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

0條評(píng)論

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