亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

閉包有話說

techstay / 2109人閱讀

摘要:請(qǐng)用一句話描述什么是閉包,并寫出代碼進(jìn)行說明。閉包的使用場景使用閉包可以在中模擬塊級(jí)作用域閉包可以用于在對(duì)象中創(chuàng)建私有變量。

引言

剛學(xué)習(xí)前端的時(shí)候,看到閉包這個(gè)詞,總是一臉懵逼,面試的時(shí)候,問到這個(gè)問題,也是回答的含含糊糊,總感覺有層隔膜,覺得這個(gè)概念很神奇,如果能掌握,必將功力大漲。其實(shí),閉包沒有這么神秘,它無處不在。

一個(gè)簡短的的問題

首先,來看一個(gè)問題。

請(qǐng)用一句話描述什么是閉包,并寫出代碼進(jìn)行說明。

如果能毫不猶豫的說出來,并能給出解釋,那下面文字對(duì)你來說就沒有往下看的必要了。
就這個(gè)問題,結(jié)合我查閱的資料和經(jīng)驗(yàn),在這里簡單的說一下,如果哪里有不對(duì)的,歡迎指正。

先回答上面的問題,什么是閉包。

閉包是一個(gè)概念,它描述了函數(shù)執(zhí)行完畢后,依然駐留內(nèi)存的現(xiàn)象。

代碼描述:

function foo() {

    var a = 2;

    function bar(){
        console.log(a);
    }

    return bar;
}

var test = foo();
test(); //2

上面這段代碼,清晰的展示了閉包。

函數(shù) bar() 的詞法作用域能夠訪問 foo()的內(nèi)部作用域。然后我們將bar()函數(shù)本身當(dāng)作一個(gè)值類型進(jìn)行傳遞。上面這個(gè)例子,我們將bar() 所引用的函數(shù)對(duì)象本身作為返回值。

foo() 執(zhí)行完畢之后, 其內(nèi)部作用域并沒有被銷毀,因?yàn)閎ar()依然保持著對(duì)內(nèi)部作用域的引用,拜bar()的位置所賜,它擁有涵蓋foo()內(nèi)部作用域的閉包,使得該作用域能夠一直存活,以供bar()在之后的任何時(shí)間進(jìn)行引用。這個(gè)引用,其實(shí)就是閉包。
也正是這個(gè)原因,test被實(shí)際調(diào)用的時(shí)候,它可以訪問定義時(shí)的詞法作用域,所以,才能訪問到a.

函數(shù)傳遞也可以是間接的:

    var fn;
    function foo(){

        var a = 2;

        function baz() {
            console.log( a );
        }
        fn = baz; //將baz 分配給全局變量
    }

    function bar(){
        fn();
    }
    foo();
    bar(); //2

所以,無論通過何種手段將內(nèi)部函數(shù)傳遞到其所在的詞法作用域外,它都會(huì)持有對(duì)原始定義作用域的引用。也就是說,無論在什么地方執(zhí)行這個(gè)函數(shù),都會(huì)使用閉包。也是這個(gè)原因,我們才可以很方便的使用回調(diào)函數(shù)而不用關(guān)心其具體細(xì)節(jié)。

其實(shí),在定時(shí)器,事件監(jiān)聽器,ajax請(qǐng)求, 跨窗口通信,Web Workers 或者任何其他的同步 或 異步任務(wù)中,只要使用了回調(diào)函數(shù),實(shí)際上就是在使用閉包。

到這里,或許你已經(jīng)對(duì)閉包有個(gè)大概的了解,下面我再舉幾個(gè)例子來幫你加深對(duì)閉包的認(rèn)識(shí)。

幾個(gè)更具體的例子

首先,就先看一下所謂的立即執(zhí)行函數(shù).

var a = 2;

(function IIFE() { 
   console.log(a); 
 })();

//2

這個(gè)立即執(zhí)行函數(shù)通常被認(rèn)為是經(jīng)典的閉包例子,它可以正常工作,但嚴(yán)格意義上講,它并不是閉包。
為什么呢?

因?yàn)檫@個(gè)IIFE函數(shù)并不是在它本身的詞法作用域之外執(zhí)行的。它在定義時(shí)所在的作用域中執(zhí)行了。而且,變量a 是通過普通的詞法作用域查找的,而不是通過閉包。

另一個(gè)用來說明閉包的例子是循環(huán)。

    
  • some text1
  • some text2
  • some text3
  • var handler = function(nodes) {
    
        for(var i = 0, l = nodes.length; i < l ; i++) {
            
            nodes[i].onclick = function(){
    
                console.log(i);
    
            }
        }
    }
    
    var tabs = document.querySelectorAll(".tabs .tab");
        handler(tabs);

    我們預(yù)期的結(jié)果是log 0 ,1,2;

    執(zhí)行之后的結(jié)果卻是是三個(gè)3;

    這是為什么呢?

    首先解釋下這個(gè)3是怎么來的,

    看一下循環(huán)體,循環(huán)的終止條件是 i < l , 首次條件成立時(shí) i 的值是3 。
    因此 ,輸出顯示的是循環(huán)結(jié)束時(shí) i 的最終值。 根據(jù)作用域的工作原理,盡管循環(huán)中的函數(shù)是在各個(gè)迭代中分別定義的,但是它們都被封閉在一個(gè)共享的全局作用域中,因此實(shí)際上是只有一個(gè)i.

    handler 函數(shù)的本意是想把唯一的i傳遞給事件處理器,但是失敗了。
    因?yàn)槭录幚砥骱瘮?shù)綁定了 i本身 ,而不是函數(shù)在構(gòu)造時(shí)的i的值.

    知道了這個(gè)之后,我們可以做出相應(yīng)的調(diào)整:

    var handler = function(nodes) {
    
        var helper = function(i){
            return function(e){
                console.log(i); // 0 1 2
            }
        }
    
        for(var i = 0, l = nodes.length; i < l ; i++) {
            
            nodes[i].onclick = helper(i);
        }
    }
    
    

    在循環(huán)外創(chuàng)建一個(gè)輔助函數(shù),讓這個(gè)輔助函數(shù)在返回一個(gè)綁定了當(dāng)前i的值的函數(shù),這樣就不會(huì)混淆了。

    明白了這點(diǎn),就會(huì)發(fā)現(xiàn),上面的處理就是為了創(chuàng)建一個(gè)新的作用域,換句話說,每次迭代我們都需要一個(gè)塊作用域.

    說到塊作用域,就不得不提一個(gè)詞,那就是let.

    所以,如果你不想過多的使用閉包,就可以使用let

    var handler = function(nodes) {
    
        for(let i = 0, l = nodes.length; i < l ; i++) {
            
            //nodes[i].index = i;
    
            nodes[i].onclick = function(){
    
                console.log(i); // 0 1 2
    
    
            }
        }
    }
    jQuery中的閉包

    先來看個(gè)例子

         var sel = $("#con"); 
         setTimeout( function (){ 
             sel.css({background:"gray"}); 
         }, 2000);
    

    上邊的代碼使用了 jQuery 的選擇器,找到 id 為 con 的元素,注冊(cè)計(jì)時(shí)器,兩秒之后,將背景色設(shè)置為灰色。

    這個(gè)代碼片段的神奇之處在于,在調(diào)用了 setTimeout 函數(shù)之后,con 依舊被保持在函數(shù)內(nèi)部,當(dāng)兩秒鐘之后,id 為 con 的 div 元素的背景色確實(shí)得到了改變。應(yīng)該注意的是,setTimeout 在調(diào)用之后已經(jīng)返回了,但是 con 沒有被釋放,這是因?yàn)?con 引用了全局作用域里的變量 con。

    以上的例子幫助我們了解了更多關(guān)于閉包的細(xì)節(jié),下面我們就深入閉包世界探尋一番。

    深入理解閉包

    首先看一個(gè)概念-執(zhí)行上下文(Execution Context)。

    執(zhí)行上下文是一個(gè)抽象的概念,ECMAScript 規(guī)范使用它來追蹤代碼的執(zhí)行。它可能是你的代碼第一次執(zhí)行或執(zhí)行的流程進(jìn)入函數(shù)主體時(shí)所在的全局上下文。

    在任意一個(gè)時(shí)間點(diǎn),只能有唯一一個(gè)執(zhí)行上下文在運(yùn)行之中。

    這就是為什么 JavaScript 是“單線程”的原因,意思就是一次只能處理一個(gè)請(qǐng)求。

    一般來說,瀏覽器會(huì)用來保存這個(gè)執(zhí)行上下文。

    是一種“后進(jìn)先出” (Last In First Out) 的數(shù)據(jù)結(jié)構(gòu),即最后插入該棧的元素會(huì)最先從棧中被彈出(這是因?yàn)槲覀冎荒軓臈5捻敳坎迦牖騽h除元素)。

    當(dāng)前的執(zhí)行上下文,或者說正在運(yùn)行中的執(zhí)行上下文永遠(yuǎn)在棧頂。

    當(dāng)運(yùn)行中的上下文被完全執(zhí)行以后,它會(huì)由棧頂彈出,使得下一個(gè)棧頂?shù)捻?xiàng)接替它成為正在運(yùn)行的執(zhí)行上下文。

    除此之外,一個(gè)執(zhí)行上下文正在運(yùn)行并不代表另一個(gè)執(zhí)行上下文需要等待它完成運(yùn)行之后才可以開始運(yùn)行。

    有時(shí)會(huì)出現(xiàn)這樣的情況,一個(gè)正在運(yùn)行中的上下文暫停或中止,另外一個(gè)上下文開始執(zhí)行。暫停的上下文可能在稍后某一時(shí)間點(diǎn)從它中止的位置繼續(xù)執(zhí)行。

    一個(gè)新的執(zhí)行上下文被創(chuàng)建并推入棧頂,成為當(dāng)前的執(zhí)行上下文,這就是執(zhí)行上下文替代的機(jī)制。

    當(dāng)我們有很多執(zhí)行上下文一個(gè)接一個(gè)地運(yùn)行時(shí)——通常情況下會(huì)在中間暫停然后再恢復(fù)運(yùn)行——為了能很好地管理這些上下文的順序和執(zhí)行情況,我們需要用一些方法來對(duì)其狀態(tài)進(jìn)行追蹤。而實(shí)際上也是如此,根據(jù)ECMAScript的規(guī)范,每個(gè)執(zhí)行上下文都有用于跟蹤代碼執(zhí)行進(jìn)程的各種狀態(tài)的組件。包括:

    代碼執(zhí)行狀態(tài):任何需要開始運(yùn)行,暫停和恢復(fù)執(zhí)行上下文相關(guān)代碼執(zhí)行的狀態(tài)
    函數(shù):上下文中正在執(zhí)行的函數(shù)對(duì)象(正在執(zhí)行的上下文是腳本或模塊的情況下可能是null)

    Realm:一系列內(nèi)部對(duì)象,一個(gè)ECMAScript全局環(huán)境,所有在全局環(huán)境的作用域內(nèi)加載的ECMAScript代碼,和其他相關(guān)的狀態(tài)及資源。

    詞法環(huán)境:用于解決此執(zhí)行上下文內(nèi)代碼所做的標(biāo)識(shí)符引用。

    變量環(huán)境:一種詞法環(huán)境,該詞法環(huán)境的環(huán)境記錄保留了變量聲明時(shí)在執(zhí)行上下文中創(chuàng)建的綁定關(guān)系。

    模塊與閉包

    現(xiàn)在的開發(fā)都離不開模塊化,下面說說模塊是如何利用閉包的。

    先看一個(gè)實(shí)際中的例子。
    這是一個(gè)統(tǒng)計(jì)模塊,看一下代碼:

        define("components/webTrends", ["webTrendCore"], function(require,exports, module) {
        
        
            var webTrendCore = require("webTrendCore");  
            var webTrends = {
                 init:function (obj) {
                     var self = this;
                    self.dcsGetId();
                    self.dcsCollect();
                },
        
                 dcsGetId:function(){
                    if (typeof(_tag) != "undefined") {
                     _tag.dcsid="dcs5w0txb10000wocrvqy1nqm_6n1p";
                     _tag.dcsGetId();
                    }
                },
        
                dcsCollect:function(){
                     if (typeof(_tag) != "undefined") {
                        _tag.DCSext.platform="weimendian";
                        if(document.readyState!="complete"){
                        document.onreadystatechange = function(){
                            if(document.readyState=="complete") _tag.dcsCollect()
                            }
                        }
                        else _tag.dcsCollect()
                    }
                }
        
            };
        
          module.exports = webTrends;
        
        })
    

    在主頁面使用的時(shí)候,調(diào)用一下就可以了:

    var webTrends = require("webTrends");
    webTrends.init();
    

    在定義的模塊中,我們暴露了webTrends對(duì)象,在外面調(diào)用返回對(duì)象中的方法就形成了閉包。

    模塊的兩個(gè)必要條件:

    必須有外部的封閉函數(shù),該函數(shù)必須至少被調(diào)用一次

    封閉函數(shù)必須返回至少一個(gè)內(nèi)部函數(shù),這樣內(nèi)部函數(shù)才能在私有作用域中形成閉包,并且可以訪問或者修改私有的狀態(tài)。

    性能考量

    如果一個(gè)任務(wù)不需要使用閉包,那最好不要在函數(shù)內(nèi)創(chuàng)建函數(shù)。
    原因很明顯,這會(huì) 拖慢腳本的處理速度,加大內(nèi)存消耗 。

    舉個(gè)例子,當(dāng)需要?jiǎng)?chuàng)建一個(gè)對(duì)象時(shí),方法通常應(yīng)該和對(duì)象的原型關(guān)聯(lián),而不是定義到對(duì)象的構(gòu)造函數(shù)中。 原因是 每次構(gòu)造函數(shù)被調(diào)用, 方法都會(huì)被重新賦值 (即 對(duì)于每個(gè)對(duì)象創(chuàng)建),這顯然是一種不好的做法。

    看一個(gè)能說明問題,但是不推薦的做法:

        function MyObject(name, message) {
        
          this.name = name.toString();
          this.message = message.toString();
          
          this.getName = function() {
            return this.name;
          };
        
          this.getMessage = function() {
            return this.message;
          };
        }
    

    上面的代碼并沒有很好的利用閉包,我們來改進(jìn)一下:

        function MyObject(name, message) {
          this.name = name.toString();
          this.message = message.toString();
        }
        
        MyObject.prototype = {
          getName: function() {
            return this.name;
          },
          getMessage: function() {
            return this.message;
          }
        };
    

    好一些了,但是不推薦重新定義原型,再來改進(jìn)下:

    function MyObject(name, message) {
        this.name = name.toString();
        this.message = message.toString();
    }
    
    MyObject.prototype.getName = function() {
           return this.name;
    };
    
    MyObject.prototype.getMessage = function() {
       return this.message;
    };
    

    很顯然,在現(xiàn)有的原型上添加方法是一種更好的做法。

    上面的代碼還可以寫的更簡練:

        function MyObject(name, message) {
            this.name = name.toString();
            this.message = message.toString();
        }
        
        (function() {
            this.getName = function() {
                return this.name;
            };
            this.getMessage = function() {
                return this.message;
            };
        }).call(MyObject.prototype);
    

    在前面的三個(gè)示例中,繼承的原型可以由所有對(duì)象共享,并且在每個(gè)對(duì)象創(chuàng)建時(shí)不需要定義方法定義。如果想看更多細(xì)節(jié),可以參考對(duì)象模型。

    閉包的使用場景:

    使用閉包可以在JavaScript中模擬塊級(jí)作用域;

    閉包可以用于在對(duì)象中創(chuàng)建私有變量。

    閉包的優(yōu)缺點(diǎn)

    優(yōu)點(diǎn):

    邏輯連續(xù),當(dāng)閉包作為另一個(gè)函數(shù)調(diào)用的參數(shù)時(shí),避免你脫離當(dāng)前邏輯而多帶帶編寫額外邏輯。

    方便調(diào)用上下文的局部變量。

    加強(qiáng)封裝性,第2點(diǎn)的延伸,可以達(dá)到對(duì)變量的保護(hù)作用。

    缺點(diǎn):

    內(nèi)存浪費(fèi)。這個(gè)內(nèi)存浪費(fèi)不僅僅因?yàn)樗qv內(nèi)存,對(duì)閉包的使用不當(dāng)會(huì)造成無效內(nèi)存的產(chǎn)生。

    結(jié)語

    前面對(duì)閉包做了一些簡單的解釋,最后再總結(jié)下,其實(shí)閉包沒什么特別的,其特點(diǎn)是:

    函數(shù)嵌套函數(shù)

    函數(shù)內(nèi)部可以訪問到外部的變量或者對(duì)象

    避免了垃圾回收

    歡迎交流,以上 ;-)

    參考資料

    讓我們一起學(xué)習(xí)JavaScript閉包吧

    弄懂JavaScript的作用域和閉包

    Closures

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

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

    相關(guān)文章

    • JS 中的閉包是什么?

      摘要:大名鼎鼎的閉包面試必問。閉包的作用是什么。看到閉包在哪了嗎閉包到底是什么五年前,我也被這個(gè)問題困擾,于是去搜了并總結(jié)下來。關(guān)于閉包的謠言閉包會(huì)造成內(nèi)存泄露錯(cuò)。閉包里面的變量明明就是我們需要的變量,憑什么說是內(nèi)存泄露這個(gè)謠言是如何來的因?yàn)椤? 本文為饑人谷講師方方原創(chuàng)文章,首發(fā)于 前端學(xué)習(xí)指南。 大名鼎鼎的閉包!面試必問。請(qǐng)用自己的話簡述 什么是「閉包」。 「閉包」的作用是什么。 首先...

      Enlightenment 評(píng)論0 收藏0
    • 【譯】JavaScript是如何工作的:內(nèi)存管理 + 如何處理4個(gè)常見的內(nèi)存泄露

      摘要:本文作為第三篇,將會(huì)討論另一個(gè)開發(fā)者容易忽視的重要主題內(nèi)存管理。我們也會(huì)提供一些關(guān)于如何處理內(nèi)存泄露的技巧。這是當(dāng)前整型和雙精度的大小。然而,這是一組可以收集的內(nèi)存空間的近似值。 本文轉(zhuǎn)載自:眾成翻譯譯者:Leslie Wang審校: 為之漫筆鏈接:http://www.zcfy.cc/article/4211原文:https://blog.sessionstack.com/how-j...

      IntMain 評(píng)論0 收藏0
    • 什么是閉包?為什么要閉包?使用閉包應(yīng)注意什么

      摘要:一什么是閉包閉包是指有權(quán)訪問另一個(gè)函數(shù)作用域中的變量的函數(shù)。就是創(chuàng)建了一個(gè)匿名函數(shù)調(diào)用函數(shù)解除對(duì)匿名函數(shù)的引用,以便釋放內(nèi)存 一、什么是閉包? 閉包是指有權(quán)訪問另一個(gè)函數(shù)作用域中的變量的函數(shù)。創(chuàng)建閉包的常見方式就是在一個(gè)函數(shù)內(nèi)部創(chuàng)建另一個(gè)函數(shù)。 二、為什么要閉包 說明:變量分為全局變量的局部變量,全局變量的作用域?yàn)槿肿饔糜颍植孔兞孔饔糜驗(yàn)榫植孔饔糜?。之前一篇文章關(guān)于作用域鏈給了介紹...

      raledong 評(píng)論0 收藏0
    • JS閉包

      摘要:閉包是什么首先,放一個(gè)概念函數(shù)加函數(shù)內(nèi)部能訪問到的局部變量就組成了一個(gè)閉包那閉包又有什么作用呢閉包常常用來間接訪問一個(gè)變量。其實(shí)這是翻譯問題,閉包的原文是,跟包沒有任何關(guān)系。所以函數(shù)套函數(shù)只是為了造出一個(gè)局部變量,跟閉包無關(guān)。 JS閉包是什么? 首先,放一個(gè)概念: 函數(shù) 加 函數(shù)內(nèi)部能訪問到的局部變量 就組成了一個(gè)閉包 那閉包又有什么作用呢? 閉包常常用來「間接訪問一個(gè)變量」。...

      ACb0y 評(píng)論0 收藏0
    • [JS]《你不知道的Javascript·上》——詞法作用域和閉包

      摘要:吐槽一下,閉包這個(gè)詞的翻譯真是有很大的誤解性啊要說閉包,要先說下詞法作用域。閉包兩個(gè)作用通過閉包,在外部環(huán)境訪問內(nèi)部環(huán)境的變量。閉包使得函數(shù)可以繼續(xù)訪問定義時(shí)的詞法作用域。 閉包是真的讓人頭暈啊,看了很久還是覺得很模糊。只能把目前自己的一些理解先寫下來,這其中必定包含著一些錯(cuò)誤,待日后有更深刻的理解時(shí)再作更改。 吐槽一下,閉包這個(gè)詞的翻譯真是有很大的誤解性啊…… 要說閉包,要先說下詞法...

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

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

    0條評(píng)論

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