站長(zhǎng)資訊網(wǎng)
        最全最豐富的資訊網(wǎng)站

        你必須了解的JavaScript閉包

        本篇文章給大家?guī)?lái)了關(guān)于JavaScript閉包的學(xué)習(xí)筆記,其中包括了閉包與方法棧以及閉包的作用,希望對(duì)大家有幫助。

        你必須了解的JavaScript閉包

        從定義上來(lái)講,它是一個(gè)腳本語(yǔ)言,而且是一個(gè)相對(duì)容易學(xué)習(xí)的腳本語(yǔ)言。不需要太多的專(zhuān)業(yè)知識(shí),你也能夠在一定程度上使用js(JavaScript的簡(jiǎn)寫(xiě))代碼。

        當(dāng)然如果你已經(jīng)學(xué)習(xí)了一前端知識(shí),你應(yīng)該能理解這個(gè)工具的作用,這是一個(gè)非常方便的顯示頁(yè)面元素之間間距的工具。你看,你僅僅是進(jìn)行了一些簡(jiǎn)單的瀏覽器操作,甚至你無(wú)法理解上述代碼的內(nèi)容,但你剛剛確確實(shí)實(shí)的嵌入了一段js代碼在你所在的頁(yè)面中(顯然它是無(wú)害的,請(qǐng)放心使用)感謝up主CodingStartup起碼課的視頻【有了它,把網(wǎng)頁(yè)做到跟設(shè)計(jì)圖一樣】以及up主ArcRain在視頻下方的回復(fù)

        這篇學(xué)習(xí)筆記的目的是記錄我自己對(duì)于js學(xué)習(xí)路程中的一些感悟和體會(huì),以及一些我自己認(rèn)為的小技巧,而不是為了教學(xué),所以其中的部分內(nèi)容的原理我并不會(huì)給出答案,有可能是我沒(méi)法準(zhǔn)確的描述,有可能是我還沒(méi)弄懂,本人水平相當(dāng)有限,如果文字中有錯(cuò)誤的部分歡迎大家指摘。

        1. 學(xué)習(xí)JavaScript的契機(jī)

        正式學(xué)習(xí)JavaScript是在培訓(xùn)班,沒(méi)錯(cuò)我是從培訓(xùn)班出來(lái)的,并不是科班出身,可以說(shuō)是非常的草根了。我學(xué)習(xí)的時(shí)候ES6標(biāo)準(zhǔn)還并未普及,變量命名還在用非常傳統(tǒng)的var,學(xué)習(xí)的第一段代碼是經(jīng)典的console.log('Hello,world!'),當(dāng)然它是在控制臺(tái)上打印出來(lái)的。

        當(dāng)然,在培訓(xùn)機(jī)構(gòu)中的JavaScript內(nèi)容講的是非常的淺顯,只有最為基礎(chǔ)的變量定義與命名,function聲明,回調(diào)函數(shù),ajax以及最為基礎(chǔ)的dom操作。顯然這些內(nèi)容對(duì)于工作完全不夠用的。

        對(duì)于js學(xué)習(xí)的‘進(jìn)修’機(jī)會(huì)來(lái)源于我的工作,在工作中我第一次知道了node這個(gè)東西,也了解到即便是js也是可以做后臺(tái)的(我是做的JAVA培訓(xùn)),也開(kāi)始逐漸接觸到了一些ES6的標(biāo)準(zhǔn)。當(dāng)然這些都是后話(huà),最開(kāi)始我接觸到最大的障礙是這貨。

        2.‘惡心’的閉包

        啊,對(duì)我只有那么一丁丁點(diǎn)基礎(chǔ)的我,完全無(wú)法理解我們公司自己封裝的jsonp代碼,它是長(zhǎng)這個(gè)樣子的。

          var jsonp = (function(){         var JSONP;        return function(url){            if (JSONP) {              document.getElementsByTagName("head")[0].removeChild(JSONP);           }          JSONP = document.createElement("script");           JSONP.type = "text/javascript";           JSONP.src = url;           document.getElementsByTagName("head")[0].appendChild(JSONP);        }      }())

        當(dāng)然,現(xiàn)在瀏覽器上已經(jīng)無(wú)法通過(guò)控制臺(tái)直接使用這個(gè)方法了,為了防止XSS攻擊瀏覽器已經(jīng)禁止這樣注入代碼了,但是在服務(wù)器上還是可以用的,當(dāng)然,這些都不是重點(diǎn)。

        重點(diǎn)是這里

            if (JSONP) {        //dosome  }

        如果你和我當(dāng)初一樣,不知道什么叫閉包或者對(duì)閉包一知半解,那么,對(duì)于這里你應(yīng)該也會(huì)產(chǎn)生疑問(wèn),思路大約是這樣的

        第2行定義了JSONP但是沒(méi)有賦值,現(xiàn)在JSONP值為null,第三行返回了一個(gè)方法,第四行檢測(cè)JSONP值是否為空,如果不為空則做了一些事情,好了,后面可以不用看了,這個(gè)if白寫(xiě)了,它百分百進(jìn)不去!

        你看嘛,前面也沒(méi)有賦值,然后直接判斷,那它明明就是null。但是實(shí)際使用的時(shí)候你會(huì)發(fā)現(xiàn),這個(gè)地方第一次調(diào)用確實(shí)不會(huì)進(jìn)入這個(gè)分支,但只要你調(diào)用了第二次,,它就百分百會(huì)進(jìn)入這個(gè)分支。

        // 這個(gè)是一個(gè)可以在控制臺(tái)輸出的閉包版本,你可以自己試一下 var closedhull = (function() {     let name = null; // 這里直接賦值為null     return function(msg){         if(name) {             console.log('name:', name)             return name += msg;         }         return name = msg;     } }()) closedhull('我是第一句。') //我是第一句。 closedhull('我是第二句。') //我是第一句。我是第二句。

        上面這個(gè)例子運(yùn)行后,無(wú)論是從console.log()亦或是返回值上都不難看出,確實(shí)進(jìn)入了if(name)的分支,這個(gè)就是閉包的表現(xiàn)。這里給出一下閉包的定義

        閉包就是能夠讀取其他函數(shù)內(nèi)部變量的函數(shù)。

        3.閉包的樣子到底是什么樣的

        好了,看過(guò)閉包是個(gè)啥了,先不說(shuō)會(huì)不會(huì)用,至少,算是見(jiàn)過(guò)了,閉包有個(gè)顯著的特征return function(){}

        不是!

        它的顯著特征是在function內(nèi)的function!

        觀(guān)察以下方法

        /*第一個(gè)案例*/ function test1(){     // a應(yīng)該在方法運(yùn)行結(jié)束后銷(xiāo)毀     let a = 1;     return {         add: function(){             return ++a;         }     } } let a = test1(); a.add()//2 a.add()//3 /*第二個(gè)案例*/ (function(){     // b應(yīng)該在方法運(yùn)行結(jié)束后銷(xiāo)毀     let b = 1,         timer = setInterval(()=>{         console.log(++b)     }, 2000)     setTimeout(()=>{         clearInterval(timer)     }, 10000) })()// 2 3 4 5 6 /*第三個(gè)案例*/ function showMaker(obj){     // obj應(yīng)該在方法運(yùn)行結(jié)束后銷(xiāo)毀     return function(){         console.log(JSON.stringify(obj))     } } let shower = showMaker({a:1}) // 顯然這里你還能看到他 shower(); // {"a":1} /*第四個(gè)案例*/ let outObj = (function(){     let c = 'hello',         obj = {};     Object.defineProperty(obj, 'out', {         get(){             return c;         },         set(v){             c = v;         }     });     return obj })() outObj.out // 可以讀取并設(shè)置c的值

        這四個(gè)都是閉包,他們都具備方法中的方法這一特性。

        4.閉包與方法棧(對(duì)原理不感興趣可以略過(guò))

        閉包的定義,1. 可以在變量的作用域外訪(fǎng)問(wèn)該變量。2. 通過(guò)某種手段延長(zhǎng)一個(gè)局部變量的生命周期。3. 讓一個(gè)局部變量的存活時(shí)間超過(guò)它的時(shí)間循環(huán)執(zhí)行時(shí)間

        3中由于涉及到了事件循環(huán)概念,之后涉及到時(shí)會(huì)去講的,這里主要討論前兩種方式的定義。

        一下內(nèi)容如果你知道方法棧是個(gè)啥了就可以跳過(guò)了

        局部作用域:在ES6之前,一般指一個(gè)方法內(nèi)部(從參數(shù)列表開(kāi)始,到方法體的括號(hào)結(jié)束為止),ES6中增加let關(guān)鍵字后,在使用let的情況下是指在一個(gè){}中的范圍內(nèi)(顯然,你不能在隱式的{}中使用let,編譯器會(huì)禁止你做出這種行為的,因?yàn)闆](méi)有{}就沒(méi)有塊級(jí)作用域),咱們這里為了簡(jiǎn)化討論內(nèi)容,暫且不把let的塊級(jí)作用域算作閉包的范疇(其實(shí)應(yīng)該算,不過(guò)意義不大,畢竟,你可以在外層塊聲明它。天啊,JS的命名還沒(méi)擁擠到需要在一個(gè)方法內(nèi)再去防止污染的程度。)

        局部變量:區(qū)別于全局變量,全局變量會(huì)在某些時(shí)候被意外額創(chuàng)造和使用,這令人非常的…惱火和無(wú)助。局部變量就是在局部作用域下使用變量聲明關(guān)鍵字聲明出來(lái)的變量,應(yīng)該很好理解。

        局部變量的生命周期:好了,你在一個(gè)局部作用域中通過(guò)關(guān)鍵字(var const let等)聲明了一個(gè)變量,然后給它賦值,這個(gè)局部變量在這個(gè)局部作用域中冒險(xiǎn)就開(kāi)始了,它會(huì)被使用,被重新賦值(除了傲嬌的const小姐外),被調(diào)用(如果它是個(gè)方法),這個(gè)局部變量的本質(zhì)是一個(gè)真實(shí)的值,區(qū)別在于如果它是個(gè)對(duì)象(對(duì)象,數(shù)組,方法都是對(duì)象)那么,它其實(shí)本質(zhì)是一個(gè)地址的指針。如果它一個(gè)基礎(chǔ)類(lèi)型,那么它就是那個(gè)真實(shí)的值。它之所以存活是因?yàn)樗袀€(gè)住所。內(nèi)存。

        局部作用域與內(nèi)存:每當(dāng)出現(xiàn)一個(gè)局部作用域,一個(gè)方法棧就被申請(qǐng)了出來(lái),在這個(gè)方法棧大概長(zhǎng)這樣子

        |  data5 | |  data4 | |  data3 | |  data2 | |__data1_|

        當(dāng)然,它是能夠套娃的,長(zhǎng)這個(gè)樣子

        |  | d2 |  | |  |_d1_|  | |  data3   | |  data2   | |__data1___|

        如果上面的東西是在太過(guò)于抽象,那么,我可以用實(shí)際案例展示一下

        function stack1(){     var data1,         data2,         data3,         data4,         data5 } function stack2(){     var data1,         data2,         data3;     function stackInner(){         var d1,             d2;     } }

        如果方法棧能夠直觀(guān)的感受的話(huà),大約就是這個(gè)樣子,咱們重點(diǎn)來(lái)分析stack2的這種情況,同時(shí)寫(xiě)一點(diǎn)實(shí)際內(nèi)容進(jìn)去

        function stack2(){     var data1 = '1',         data2 = {x: '2'},         data3 = '3';     function stackInner(){         var d1 = '4',             d2 = {y: '5'};     }     stackInner() } stack2()

        顯然其中data1,data3,d1持有的是基本類(lèi)型(string),data2,d2持有的是引用類(lèi)型(object),反應(yīng)到圖上

        運(yùn)行時(shí)的方法棧的樣子

                    |------>{y: '5'}             |    |->{x: '2'}     |  | d2-|   || |     |  |_d1='4'_|| |     |  data3='3' | |     |  data2 ----| |     |__data1='1'___|

        畫(huà)有點(diǎn)抽象…就這樣吧。具體對(duì)象在哪呢?他們?cè)谝粋€(gè)叫堆的地方,不是這次的重點(diǎn),還是先看方法棧內(nèi)的這些變量,運(yùn)行結(jié)束后,按照先進(jìn)后出的原則,把棧內(nèi)的局部變量一個(gè)一個(gè)的銷(xiāo)毀,同時(shí)堆里的兩個(gè)對(duì)象,由于引用被銷(xiāo)毀,沒(méi)了繼續(xù)存在的意義,等待被垃圾回收。

        接下來(lái)咱們要做兩件事情:

        • d1不再等于4了,而是引用data1

        • return stackInner 而不是直接調(diào)用

        這樣閉包就完成了

        function stack2(){     var data1 = {msg: 'hello'},         data2 = {x: '2'},         data3 = '3';     function stackInner(){         var d1 = data1,             d2 = {y: '5'};     }     return stackInner } var out = stack2()

        這里有一個(gè)要點(diǎn),d2賦值給data1一定是在stackInner中完成的,原因?因?yàn)樵賡tackInner方法中d2才被聲明出來(lái),如果你在stack2中d1 = data1那么恭喜你,你隱式的聲明了一個(gè)叫d1的全局變量,而且在stackInner由于變量屏蔽的原因,你也看不到全局上的d2,原本計(jì)劃的閉包完全泡湯。

        變量屏蔽:不同作用域中相同名稱(chēng)的變量就會(huì)觸發(fā)變量屏蔽。

        看看?,F(xiàn)在的樣子

        運(yùn)行時(shí)的方法棧的樣子

                       |------>{y: '5'} out<---|       | |----|     |  |  | d2-| | |  |  |     |  |--|_d1---|_|  |  |     |     data3='3'   |  |     |     data2(略)   |  |     |_____data1<------|__|

        好了,這個(gè)圖可以和我們永別了,如果有可能,我后面會(huì)用畫(huà)圖工具替代,這么畫(huà)圖實(shí)在是太過(guò)邪典了。

        這里涉及到了方法棧的一個(gè)特性,就是變量的穿透性,外部變量可以在內(nèi)部的任意位置使用,因?yàn)樵賰?nèi)部執(zhí)行結(jié)束前,外部變量會(huì)一直存在。

        由于stackInner被外部的out引用,導(dǎo)致這個(gè)對(duì)象不會(huì)隨著方法棧的結(jié)束而銷(xiāo)毀,接下來(lái),最神奇的事情來(lái)了,由于stackInner這對(duì)象沒(méi)有銷(xiāo)毀,它內(nèi)部d1依然保有data1所對(duì)應(yīng)數(shù)據(jù)的引用,d1,d2一定會(huì)活下來(lái),因?yàn)樗麄兊陌职謘tackInner活下來(lái)了,data1也以某種形式活了下來(lái)。

        為什么說(shuō)是某種形式,因?yàn)?,本質(zhì)上來(lái)說(shuō)data1還是被銷(xiāo)毀了。沒(méi)錯(cuò),只不過(guò),data1所引用的那個(gè)對(duì)象的地址鏈接沒(méi)有被銷(xiāo)毀,這個(gè)才是本質(zhì)。棧在調(diào)用結(jié)束后一定是會(huì)銷(xiāo)毀的。但是調(diào)用本體(方法對(duì)象)只要存在,那么內(nèi)部所引用的鏈接就不會(huì)斷。

        這個(gè)就是閉包的成因和本質(zhì)。

        5.閉包有什么用

        OK,我猜測(cè)上一個(gè)章節(jié)估計(jì)很多人都直接跳過(guò)了,其實(shí),跳過(guò)影響也不多,這個(gè)部分描述一下結(jié)論性的東西,閉包的作用。

        它的最大作用就是給你的變量一個(gè)命名空間,防止命名沖突。要知道,你的框架,你export的東西,你import進(jìn)來(lái)的東西,在編譯的時(shí)候都會(huì)變成閉包,為的就是減少你變量對(duì)全局變量的污染,一個(gè)不依賴(lài)與import export的模塊的代碼大概長(zhǎng)這個(gè)樣子

        (function(Constr, global){     let xxx = new Constr(env1, env2, env3)     global.NameSpace = xxx; })(function(parm1, parm2, parm3) {     //dosomeing     reutrn {         a: 'some1',         b: 'some2',         funcC(){             //dosome         },         funcD(){             //dosome         }     } }, window)

        當(dāng)然這種封裝代碼的風(fēng)格有多種多樣的,但是大家都盡量把一套體系的內(nèi)容都放到一個(gè)命名空間下,避免與其他框架產(chǎn)生沖突

        贊(0)
        分享到: 更多 (0)
        網(wǎng)站地圖   滬ICP備18035694號(hào)-2    滬公網(wǎng)安備31011702889846號(hào)
        主站蜘蛛池模板: 国产精品福利网站导航| 中文字幕亚洲综合精品一区| aaa级精品久久久国产片| 精品无人区无码乱码大片国产| 国产精品高清一区二区三区| 四虎精品免费永久免费视频| 国产福利精品视频自拍| 国产精品视频网| 国产精品偷伦视频观看免费| 亚洲精品第一国产综合精品99| 国产精品亚洲不卡一区二区三区 | 精品久久人妻av中文字幕| 久久久久久久久久久免费精品| 91精品国产综合久久香蕉| 国产精品久久久久久一区二区三区 | 99久久免费国产精品热| 亚洲精品无码mv在线观看网站| 欧美人与性动交α欧美精品| 国产综合成人色产三级高清在线精品发布 | 无码人妻精品中文字幕| 久久亚洲精品无码观看不卡| 国产精品白丝AV嫩草影院| 亚洲人成亚洲精品| 国产成人精品免费视频大| 国产久热精品无码激情| 久久精品亚洲日本波多野结衣| 伊在人亚洲香蕉精品区麻豆| 香港aa三级久久三级老师2021国产三级精品三级在 | 人妻精品久久久久中文字幕69| 中文精品人人永久免费| 一本色道久久88精品综合| 亚洲人午夜射精精品日韩| 在线精品无码字幕无码AV| 综合精品欧美日韩国产在线| 亚洲精品成人无限看| 人人妻人人澡人人爽人人精品电影| 亚洲国产精品高清久久久| 人妻少妇精品视频一区二区三区| 亚洲AV无码精品色午夜在线观看| 最新国产精品无码| 精品无人码麻豆乱码1区2区|