国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

前端基礎進階(四):詳細圖解作用域鏈與閉包

aikin / 1663人閱讀

摘要:之前一篇文章我們詳細說明了變量對象,而這里,我們將詳細說明作用域鏈。而的作用域鏈,則同時包含了這三個變量對象,所以的執行上下文可如下表示。下圖展示了閉包的作用域鏈。其中為當前的函數調用棧,為當前正在被執行的函數的作用域鏈,為當前的局部變量。


初學JavaScript的時候,我在學習閉包上,走了很多彎路。而這次重新回過頭來對基礎知識進行梳理,要講清楚閉包,也是一個非常大的挑戰。

閉包有多重要?如果你是初入前端的朋友,我沒有辦法直觀的告訴你閉包在實際開發中的無處不在,但是我可以告訴你,前端面試,必問閉包。面試官們常常用對閉包的了解程度來判定面試者的基礎水平,保守估計,10個前端面試者,至少5個都死在閉包上。

可是為什么,閉包如此重要,還是有那么多人沒有搞清楚呢?是因為大家不愿意學習嗎?還真不是,而是我們通過搜索找到的大部分講解閉包的中文文章,都沒有清晰明了的把閉包講解清楚。要么淺嘗輒止,要么高深莫測,要么干脆就直接亂說一通。包括我自己曾經也寫過一篇關于閉包的總結,回頭一看,不忍直視[捂臉]。

因此本文的目的就在于,能夠清晰明了得把閉包說清楚,讓讀者朋友們看了之后,就把閉包給徹底學會了,而不是似懂非懂。

一、作用域與作用域鏈

在詳細講解作用域鏈之前,我默認你已經大概明白了JavaScript中的下面這些重要概念。這些概念將會非常有幫助。

基礎數據類型與引用數據類型

內存空間

垃圾回收機制

執行上下文

變量對象與活動對象

如果你暫時還沒有明白,可以去看本系列的前三篇文章,本文文末有目錄鏈接。為了講解閉包,已經為大家做好了基礎知識的鋪墊哦。

作用域

在JavaScript中,我們可以將作用域定義為一套規則,這套規則用來管理引擎如何在當前作用域以及嵌套的子作用域中根據標識符名稱進行變量查找。

這里的標識符,指的是變量名或者函數名

JavaScript中只有全局作用域與函數作用域(因為eval我們平時開發中幾乎不會用到它,這里不討論)。

作用域與執行上下文是完全不同的兩個概念。我知道很多人會混淆他們,但是一定要仔細區分。

JavaScript代碼的整個執行過程,分為兩個階段,代碼編譯階段與代碼執行階段。編譯階段由編譯器完成,將代碼翻譯成可執行代碼,這個階段作用域規則會確定。執行階段由引擎完成,主要任務是執行可執行代碼,執行上下文在這個階段創建。

作用域鏈

回顧一下上一篇文章我們分析的執行上下文的生命周期,如下圖。

我們知道函數在調用激活時,會開始創建對應的執行上下文,在執行上下文生成的過程中,變量對象,作用域鏈,以及this的值會分別被確定。之前一篇文章我們詳細說明了變量對象,而這里,我們將詳細說明作用域鏈。

作用域鏈,是由當前環境與上層環境的一系列變量對象組成,它保證了當前執行環境對符合訪問權限的變量和函數的有序訪問。

為了幫助大家理解作用域鏈,我我們先結合一個例子,以及相應的圖示來說明。

var a = 20;

function test() {
    var b = a + 10;

    function innerTest() {
        var c = 10;
        return b + c;
    }

    return innerTest();
}

test();

在上面的例子中,全局,函數test,函數innerTest的執行上下文先后創建。我們設定他們的變量對象分別為VO(global),VO(test), VO(innerTest)。而innerTest的作用域鏈,則同時包含了這三個變量對象,所以innerTest的執行上下文可如下表示。

innerTestEC = {
    VO: {...},  // 變量對象
    scopeChain: [VO(innerTest), VO(test), VO(global)], // 作用域鏈
}

我們可以直接用一個數組來表示作用域鏈,數組的第一項scopeChain[0]為作用域鏈的最前端,而數組的最后一項,為作用域鏈的最末端,所有的最末端都為全局變量對象。

很多人會誤解為當前作用域與上層作用域為包含關系,但其實并不是。以最前端為起點,最末端為終點的單方向通道我認為是更加貼切的形容。如圖。

注意,因為變量對象在執行上下文進入執行階段時,就變成了活動對象,這一點在上一篇文章中已經講過,因此圖中使用了AO來表示。Active Object

是的,作用域鏈是由一系列變量對象組成,我們可以在這個單向通道中,查詢變量對象中的標識符,這樣就可以訪問到上一層作用域中的變量了。

二、閉包

對于那些有一點 JavaScript 使用經驗但從未真正理解閉包概念的人來說,理解閉包可以看作是某種意義上的重生,突破閉包的瓶頸可以使你功力大增。

閉包是一種特殊的對象。

它由兩部分組成。執行上下文(代號A),以及在該執行上下文中創建的函數(代號B)。

當B執行時,如果訪問了A中變量對象中的值,那么閉包就會產生。

在大多數理解中,包括許多著名的書籍,文章里都以函數B的名字代指這里生成的閉包。而在chrome中,則以執行上下文A的函數名代指閉包。

因此我們只需要知道,一個閉包對象,由A、B共同組成,在以后的篇幅中,我將以chrome的標準來稱呼。

// demo01
function foo() {
    var a = 20;
    var b = 30;

    function bar() {
        return a + b;
    }

    return bar;
}

var bar = foo();
bar();

上面的例子,首先有執行上下文foo,在foo中定義了函數bar,而通過對外返回bar的方式讓bar得以執行。當bar執行時,訪問了foo內部的變量a,b。因此這個時候閉包產生。

在基礎進階(一)中,我總結了JavaScript的垃圾回收機制。JavaScript擁有自動的垃圾回收機制,關于垃圾回收機制,有一個重要的行為,那就是,當一個值,在內存中失去引用時,垃圾回收機制會根據特殊的算法找到它,并將其回收,釋放內存。

而我們知道,函數的執行上下文,在執行完畢之后,生命周期結束,那么該函數的執行上下文就會失去引用。其占用的內存空間很快就會被垃圾回收器釋放。可是閉包的存在,會阻止這一過程。

先來一個簡單的例子。

var fn = null;
function foo() {
    var a = 2;
    function innnerFoo() {
        console.log(a);
    }
    fn = innnerFoo; // 將 innnerFoo的引用,賦值給全局變量中的fn
}

function bar() {
    fn(); // 此處的保留的innerFoo的引用
}

foo();
bar(); // 2

在上面的例子中,foo()執行完畢之后,按照常理,其執行環境生命周期會結束,所占內存被垃圾收集器釋放。但是通過fn = innerFoo,函數innerFoo的引用被保留了下來,復制給了全局變量fn。這個行為,導致了foo的變量對象,也被保留了下來。于是,函數fn在函數bar內部執行時,依然可以訪問這個被保留下來的變量對象。所以此刻仍然能夠訪問到變量a的值。

這樣,我們就可以稱foo為閉包。

下圖展示了閉包foo的作用域鏈。

我們可以在chrome瀏覽器的開發者工具中查看這段代碼運行時產生的函數調用棧與作用域鏈的生成情況。如下圖。

關于如何在chrome中觀察閉包,以及更多閉包的例子,請閱讀基礎系列(六)

在上面的圖中,紅色箭頭所指的正是閉包。其中Call Stack為當前的函數調用棧,Scope為當前正在被執行的函數的作用域鏈,Local為當前的局部變量。

所以,通過閉包,我們可以在其他的執行上下文中,訪問到函數的內部變量。比如在上面的例子中,我們在函數bar的執行環境中訪問到了函數foo的a變量。個人認為,從應用層面,這是閉包最重要的特性。利用這個特性,我們可以實現很多有意思的東西。

不過讀者朋友們需要注意的是,雖然例子中的閉包被保存在了全局變量中,但是閉包的作用域鏈并不會發生任何改變。在閉包中,能訪問到的變量,仍然是作用域鏈上能夠查詢到的變量。

對上面的例子稍作修改,如果我們在函數bar中聲明一個變量c,并在閉包fn中試圖訪問該變量,運行結果會拋出錯誤。

var fn = null;
function foo() {
    var a = 2;
    function innnerFoo() {
        console.log(c); // 在這里,試圖訪問函數bar中的c變量,會拋出錯誤
        console.log(a);
    }
    fn = innnerFoo; // 將 innnerFoo的引用,賦值給全局變量中的fn
}

function bar() {
    var c = 100;
    fn(); // 此處的保留的innerFoo的引用
}

foo();
bar();
關于這一點,很多同學把函數調用棧與作用域鏈沒有分清楚,所以有的大神看了我關于介紹執行上下文的文章時就義正言辭的說我的例子有問題,而這些評論有很大的誤導作用,為了幫助大家自己擁有能夠辨別的能力,所以我寫了基礎(六),教大家如何在chrome中觀察閉包,作用域鏈,this等。當然我也不敢100%保證我文中的例子就一定正確,所以教大家如何去辨認我認為才是最重要的。

閉包的應用場景

除了面試,在實踐中,閉包有兩個非常重要的應用場景。分別是模塊化與柯里化。

柯里化

在函數式編程中,利用閉包能夠實現很多炫酷的功能,柯里化便是其中很重要的一種。點擊了解更多關于柯里化的知識

模塊

在我看來,模塊是閉包最強大的一個應用場景。如果你是初學者,對于模塊的了解可以暫時不用放在心上,因為理解模塊需要更多的基礎知識。但是如果你已經有了很多JavaScript的使用經驗,在徹底了解了閉包之后,不妨借助本文介紹的作用域鏈與閉包的思路,重新理一理關于模塊的知識。這對于我們理解各種各樣的設計模式具有莫大的幫助。

(function () {
    var a = 10;
    var b = 20;

    function add(num1, num2) {
        var num1 = !!num1 ? num1 : a;
        var num2 = !!num2 ? num2 : b;

        return num1 + num2;
    }

    window.add = add;
})();

add(10, 20);

在上面的例子中,我使用函數自執行的方式,創建了一個模塊。add是模塊對外暴露的一個公共方法。而變量a,b被作為私有變量。在面向對象的開發中,我們常常需要考慮是將變量作為私有變量,還是放在構造函數中的this中,因此理解閉包,以及原型鏈是一個非常重要的事情。模塊十分重要,因此我會在以后的文章專門介紹,這里就暫時不多說啦。

為了驗證自己有沒有搞懂作用域鏈與閉包,這里留下一個經典的思考題,常常也會在面試中被問到。

利用閉包,修改下面的代碼,讓循環輸出的結果依次為1, 2, 3, 4, 5

for (var i=1; i<=5; i++) {
    setTimeout( function timer() {
        console.log(i);
    }, i*1000 );
}

點此查看關于此題的詳細解讀

關于作用域鏈的與閉包我就總結完了,雖然我自認為我是說得非常清晰了,但是我知道理解閉包并不是一件簡單的事情,所以如果你有什么問題,可以在評論中問我。你也可以帶著從別的地方沒有看懂的例子在評論中留言。大家一起學習進步。

前端基礎進階系列目錄

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/90521.html

相關文章

  • 前端基礎進階目錄

    摘要:不過其實簡書文章評論里有很多大家的問題以及解答,對于進一步理解文中知識幫助很大的,算是有點可惜吧。不過也希望能夠對正在學習前端的你有一些小幫助。如果在閱讀中發現了一些錯誤,請在評論里告訴我,我會及時更改。 前端基礎進階(一):內存空間詳細圖解 前端基礎進階(二):執行上下文詳細圖解 前端基礎進階(三):變量對象詳解 前端基礎進階(四):詳細圖解作用域鏈與閉包 前端基礎進階(五):全方位...

    mo0n1andin 評論0 收藏0
  • 文章分享(持續更新)

    摘要:文章分享持續更新更多資源請文章轉自一前端文章基礎篇,,前端基礎進階一內存空間詳細圖解前端基礎進階二執行上下文詳細圖解前端基礎進階三變量對象詳解前端基礎進階四詳細圖解作用域鏈與閉包前端基礎進階五全方位解讀前端基礎進階六在開發者工具中觀察函數調 文章分享(持續更新) 更多資源請Star:https://github.com/maidishike... 文章轉自:https://gith...

    whlong 評論0 收藏0
  • 前端基礎進階(六):在chrome開發者工具中觀察函數調用棧、作用鏈與閉包

    摘要:在的開發者工具中,通過斷點調試,我們能夠非常方便的一步一步的觀察的執行過程,直觀感知函數調用棧,作用域鏈,變量對象,閉包,等關鍵信息的變化。其中表示當前的局部變量對象,表示當前作用域鏈中的閉包。 showImg(https://segmentfault.com/img/remote/1460000008404321); 在前端開發中,有一個非常重要的技能,叫做斷點調試。 在chrome...

    draveness 評論0 收藏0
  • 進階2-1期】深入淺出圖解作用域鏈閉包

    摘要:本期推薦文章從作用域鏈談閉包,由于微信不能訪問外鏈,點擊閱讀原文就可以啦。推薦理由這是一篇譯文,深入淺出圖解作用域鏈,一步步深入介紹閉包。作用域鏈的頂端是全局對象,在全局環境中定義的變量就會綁定到全局對象中。 (關注福利,關注本公眾號回復[資料]領取優質前端視頻,包括Vue、React、Node源碼和實戰、面試指導) 本周開始前端進階的第二期,本周的主題是作用域閉包,今天是第6天。 本...

    levius 評論0 收藏0
  • 進階2-2期】JavaScript深入之從作用域鏈理解閉包

    摘要:使用上一篇文章的例子來說明下自由變量進階期深入淺出圖解作用域鏈和閉包訪問外部的今天是今天是其中既不是參數,也不是局部變量,所以是自由變量。 (關注福利,關注本公眾號回復[資料]領取優質前端視頻,包括Vue、React、Node源碼和實戰、面試指導) 本周正式開始前端進階的第二期,本周的主題是作用域閉包,今天是第7天。 本計劃一共28期,每期重點攻克一個面試重難點,如果你還不了解本進階計...

    simpleapples 評論0 收藏0

發表評論

0條評論

aikin

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<