• <fieldset id="8imwq"><menu id="8imwq"></menu></fieldset>
  • <bdo id="8imwq"><input id="8imwq"></input></bdo>
    最新文章專題視頻專題問答1問答10問答100問答1000問答2000關鍵字專題1關鍵字專題50關鍵字專題500關鍵字專題1500TAG最新視頻文章推薦1 推薦3 推薦5 推薦7 推薦9 推薦11 推薦13 推薦15 推薦17 推薦19 推薦21 推薦23 推薦25 推薦27 推薦29 推薦31 推薦33 推薦35 推薦37視頻文章20視頻文章30視頻文章40視頻文章50視頻文章60 視頻文章70視頻文章80視頻文章90視頻文章100視頻文章120視頻文章140 視頻2關鍵字專題關鍵字專題tag2tag3文章專題文章專題2文章索引1文章索引2文章索引3文章索引4文章索引5123456789101112131415文章專題3
    問答文章1 問答文章501 問答文章1001 問答文章1501 問答文章2001 問答文章2501 問答文章3001 問答文章3501 問答文章4001 問答文章4501 問答文章5001 問答文章5501 問答文章6001 問答文章6501 問答文章7001 問答文章7501 問答文章8001 問答文章8501 問答文章9001 問答文章9501
    當前位置: 首頁 - 科技 - 知識百科 - 正文

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

    來源:懂視網(wǎng) 責編:小采 時間:2020-11-27 20:25:09
    文檔

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

    前端基礎進階(四):詳細圖解作用域鏈與閉包:初學JavaScript的時候,我在學習閉包上,走了很多彎路。而這次重新回過頭來對基礎知識進行梳理,要講清楚閉包,也是一個非常大的挑戰(zhàn)。閉包有多重要?如果你是初入前端的朋友,我沒有辦法直觀的告訴你閉包在實際開發(fā)中的無處不在,但是我可以告訴你,前端面試
    推薦度:
    導讀前端基礎進階(四):詳細圖解作用域鏈與閉包:初學JavaScript的時候,我在學習閉包上,走了很多彎路。而這次重新回過頭來對基礎知識進行梳理,要講清楚閉包,也是一個非常大的挑戰(zhàn)。閉包有多重要?如果你是初入前端的朋友,我沒有辦法直觀的告訴你閉包在實際開發(fā)中的無處不在,但是我可以告訴你,前端面試
    1.png

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

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

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

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


    一、作用域與作用域鏈

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

    1.基礎數(shù)據(jù)類型與引用數(shù)據(jù)類型

    2.內(nèi)存空間

    3.垃圾回收機制

    4.執(zhí)行上下文

    5.變量對象與活動對象

    如果你暫時還沒有明白,可以去看本系列的前三篇文章,本文文末有目錄鏈接。為了講解閉包,我已經(jīng)為大家做好了基礎知識的鋪墊。哈哈,真是好大一出戲。

    作用域

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

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

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

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

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

    2.png

    作用域鏈

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

    3.png

    我們發(fā)現(xiàn),作用域鏈是在執(zhí)行上下文的創(chuàng)建階段生成的。這個就奇怪了。上面我們剛剛說作用域在編譯階段確定規(guī)則,可是為什么作用域鏈卻在執(zhí)行階段確定呢?

    之所有有這個疑問,是因為大家對作用域和作用域鏈有一個誤解。我們上面說了,作用域是一套規(guī)則,那么作用域鏈是什么呢?是這套規(guī)則的具體實現(xiàn)。所以這就是作用域與作用域鏈的關系,相信大家都應該明白了吧。

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

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

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

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

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

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

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

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

    4.png

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

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


    二、閉包

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

    先直截了當?shù)膾伋鲩]包的定義:當一個函數(shù)可以記住并訪問所在的作用域(全局作用域除外),并在定義該函數(shù)的作用域之外執(zhí)行時,該函數(shù)就可以稱之為一個閉包。

    簡單來說,假設函數(shù)A在函數(shù)B的內(nèi)部進行定義了,并在函數(shù)B的作用域之外執(zhí)行(不管是上層作用域,下層作用域,還有其他作用域),那么A就是一個閉包。記住這個定義,你在其他地方很難看到了。

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

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

    先來一個簡單的例子。

    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()執(zhí)行完畢之后,按照常理,其執(zhí)行環(huán)境生命周期會結(jié)束,所占內(nèi)存被垃圾收集器釋放。但是通過fn = innerFoo,函數(shù)innerFoo的引用被保留了下來,復制給了全局變量fn。這個行為,導致了foo的變量對象,也被保留了下來。于是,函數(shù)fn在函數(shù)bar內(nèi)部執(zhí)行時,依然可以訪問這個被保留下來的變量對象。所以此刻仍然能夠訪問到變量a的值。

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

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

    5.png

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

    通過閉包,我們可以訪問到函數(shù)的內(nèi)部變量。這是閉包的一種特性,但是由于在其他很多地方,被用來當成閉包的定義,這其實是不準確的。

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

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

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

    上面的例子,可以很直觀的感受到閉包的存在,但是還有一種情況的閉包,則更加隱蔽難以感受。我們來看一個例子。

    function test() {
     function bar (str) {
     console.log(str);
     }
    
     function foo (fn, string) {
     fn(string);
     }
    
     foo(bar, 'this is closure');
    }
    
    
    test();

    這個例子中,函數(shù)bar在函數(shù)test的作用域中定義,然后被作為參數(shù)傳入了函數(shù)foo中并在foo的作用域中被執(zhí)行。根據(jù)定義,我們很容易知道函數(shù)bar就是一個閉包。因為其隱蔽性,很多人并沒有意識到這就是一個閉包。這種情況,就是我們常常說的回調(diào)函數(shù)。在實際開發(fā)中,我們遇到的大多數(shù)回調(diào)函數(shù)都是閉包。

    很多時候,回調(diào)函數(shù)都是匿名函數(shù),但是要注意的是,在其他一些語言中,閉包與匿名函數(shù)是有區(qū)別的,但是JavaScript在實現(xiàn)匿名函數(shù)的時候允許形成閉包,當匿名函數(shù)作為參數(shù)傳入函數(shù)中時,匿名函數(shù)的引用會保存在改函數(shù)變量對象的arguments對象中。因此在JavaScript中,我們可以不用那么嚴格的區(qū)別閉包與匿名函數(shù)。

    閉包的應用場景

    接下來,我們來總結(jié)下,閉包的常用場景。

    延遲函數(shù)setTimeout

    我們知道setTimeout的第一個參數(shù)是一個函數(shù),第二個參數(shù)則是延遲的時間。在下面例子中,

    function fn() {
     console.log('this is test.')
    }
    var timer = setTimeout(fn, 1000);
    console.log(timer);

    執(zhí)行上面的代碼,變量timer的值,會立即輸出出來,表示setTimeout這個函數(shù)本身已經(jīng)執(zhí)行完畢了。但是一秒鐘之后,fn才會被執(zhí)行。這是為什么?

    按道理來說,既然fn被作為參數(shù)傳入了setTimeout中,那么fn將會被保存在setTimeout變量對象中,setTimeout執(zhí)行完畢之后,它的變量對象也就不存在了??墒鞘聦嵣喜⒉皇沁@樣。至少在這一秒鐘的事件里,它仍然是存在的。這正是因為閉包。

    很顯然,這是在函數(shù)的內(nèi)部實現(xiàn)中,setTimeout通過特殊的方式,保留了fn的引用,讓setTimeout的變量對象,并沒有在其執(zhí)行完畢后被垃圾收集器回收。因此setTimeout執(zhí)行結(jié)束后一秒,我們?nèi)稳荒軌驁?zhí)行fn函數(shù)。

    回調(diào)函數(shù)

    在上面的例子中,我們已經(jīng)解釋過了回調(diào)函數(shù)。所以就不再多說。

    柯里化

    在函數(shù)式編程中,利用閉包能夠?qū)崿F(xiàn)很多炫酷的功能,柯里化算是其中一種。關于柯里化,我會在以后詳解函數(shù)式編程的時候仔細總結(jié)。

    模塊

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

    (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;
    })();

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

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

    利用閉包,修改下面的代碼,讓循環(huán)輸出的結(jié)果依次為1, 2, 3, 4, 5

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

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

    聲明:本網(wǎng)頁內(nèi)容旨在傳播知識,若有侵權等問題請及時與本網(wǎng)聯(lián)系,我們將在第一時間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com

    文檔

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

    前端基礎進階(四):詳細圖解作用域鏈與閉包:初學JavaScript的時候,我在學習閉包上,走了很多彎路。而這次重新回過頭來對基礎知識進行梳理,要講清楚閉包,也是一個非常大的挑戰(zhàn)。閉包有多重要?如果你是初入前端的朋友,我沒有辦法直觀的告訴你閉包在實際開發(fā)中的無處不在,但是我可以告訴你,前端面試
    推薦度:
    標簽: 圖解 詳細 基礎
    • 熱門焦點

    最新推薦

    猜你喜歡

    熱門推薦

    專題
    Top
    主站蜘蛛池模板: 亚洲精品国产电影| 精品无码一区二区三区爱欲| 亚洲精品岛国片在线观看| 久久99热国产这有精品| 亚洲欧洲美洲无码精品VA| 国产乱码伦精品一区二区三区麻豆| 国产99视频精品免视看7| 亚洲国产精品自产在线播放| 99精品电影一区二区免费看| 国产亚洲欧美精品久久久| 自拍偷自拍亚洲精品被多人伦好爽 | 国产精品免费一区二区三区| 国产精品美女一区二区视频| 亚洲日韩精品无码专区网址| 国内精品久久久久影院网站| 中文字幕精品一区二区日本| 91国内外精品自在线播放| 国产精品无码无在线观看| 骚片AV蜜桃精品一区| 亚洲精品无码久久久久AV麻豆| 一色屋精品视频在线观看| 99久久成人国产精品免费| 香港三级精品三级在线专区| 北条麻妃国产九九九精品视频| 99久久精品毛片免费播放| 久久国产免费观看精品3| 亚洲精品午夜国产VA久久成人| 久久免费99精品国产自在现线| 一区二区三区精品| 国产精品乱码高清在线观看| 欧美精品VIDEOSSEX少妇| 亚洲综合国产精品第一页 | 日本欧美国产精品第一页久久 | 精品国产日产一区二区三区| 国产美女精品一区二区三区| 精品三级AV无码一区| 久久99精品国产麻豆| 国产日韩精品欧美一区喷水| 91视频精品全国免费观看| 国产精品福利一区二区| 99热亚洲色精品国产88|