亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

一文了解你不知道的JavaScript閉包篇

 更新時間:2022年11月11日 08:40:56   作者:霍格沃茨魔法師  
這篇文章主要為大家詳細介紹了一些你不知道的JavaScript閉包相關知識,文中的示例代碼講解詳細,對我們學習JavaScript有一定幫助,感興趣的可以跟隨小編一起學習一下

前言

JavaScript語言中有一個非常重要又難以掌握,近似神話的概念-閉包。對于有一點JavaScript使用經(jīng)驗但從未真正理解閉包概念的人來說,理解閉包可以看作是某種意義上的重生。JavaScript中閉包無處不在,我們只需要能夠識別并擁抱它。它是基于詞法作用域書寫代碼時所產(chǎn)生的自然結果,在代碼中隨處可見。

理解閉包

下面用一些代碼來解釋這個定義:

function foo(){
   var a = 2;
   function bar(){
     console.log(a);//2
   }
   bar();
}
foo()

這是閉包嗎?也許是的,但似乎這種方式對必報的定義并不能直接進行觀察,也無法明白這個代碼片段中閉包是如何工作的。我們很容易地理解詞法作用域,而閉包則隱藏在代碼之后的神秘陰影里,并不那么容易理解。

下面我們來看一段代碼,清晰的展示了閉包:

function foo(){
   var a = 2;
   function bar(){
     console.log(a)
   }
   return bar;
}
var baz = foo();
baz() //2-------這就是閉包的效果。

函數(shù)bar的詞法作用域能夠訪問foo()的內(nèi)部作用域。然后我們將bar()函數(shù)本身當作一個值類型進行傳遞。在這個例子中,我們將bar所引用的函數(shù)對象本身當作返回值。在foo()執(zhí)行后,它的返回值(bar函數(shù))賦值給變量baz并調(diào)用baz(),實際上只是通過不同的標識符引用調(diào)用了內(nèi)部的函數(shù)baz().foo內(nèi)部的bar()顯示可以被正常執(zhí)行。

在foo()執(zhí)行后,通常會期待foo()的整個內(nèi)部作用域都被銷毀,因為我們知道引擎有垃圾回收器來釋放不再使用的空間。由于foo()似乎不會在被利用,所以大腦很自然的認為會對其進行回收。

而閉包的神奇之處正是可以阻止這件事的發(fā)生。事實上內(nèi)部作用域依然存在,因此沒有被回收。誰在使用這個內(nèi)部作用域呢?原來是bar()本身在使用。所以拜bar()所聲明的位置所賜,它擁有覆蓋foo()內(nèi)部作用域的閉包,使得foo()的作用域能夠一直存活,以供bar()在之后任何時間都可以被調(diào)用。

bar()依然持有對該作用域的引用,而這個引用就叫做閉包。

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

var fn;
function foo(){
    var a = 2;
    function baz(){
        console.log(a);
    }
    fn = baz;
}
function bar(){
   fn()
}
foo();
bar(); //2-------這就是閉包!

無論通過何種手段將內(nèi)部函數(shù)傳遞到所在的詞法作用域之外,它都會持有原始定義作用域的引用,無法在何處執(zhí)行這個函數(shù)都會使用閉包。

升級版閉包

前面的代碼片段可能有些死板,并且為了解釋如何使用閉包而把代碼寫的很明顯。但其實閉包在實際操作中是個很好玩的工具,而且大家也一定都用過閉包。現(xiàn)在讓我們來搞懂這個事實:

function wait(message){
    setTimeout(function timer(){
       console.log(message);
    },1000);
}
wait ("Hello closure")

將一個內(nèi)部函數(shù)(名為timer)傳遞給setTimeout().timer具有涵蓋wait()作用域的閉包,因此還保有對變量message的引用。

wait(...)執(zhí)行1000毫秒,它的內(nèi)部作用域并不會消失,timer函數(shù)依然保有wait()作用域的閉包。這就是閉包。我不知道你在生活中都會寫什么樣的代碼,但在定時器、事件監(jiān)聽器、Ajax請求、跨窗口通信或者任何其他的異步任務,只要你使用了回調(diào)函數(shù),實際上就是在使用閉包

var a = 2;
(
  function IIFE(){
    console.log(a)
  }
)()

雖然這段代碼可以正常工作,但嚴格來講它并不是什么閉包,因為函數(shù)并不是在它本身的詞法作用域以外執(zhí)行的。它在定義時所在的作用域中執(zhí)行(而外部作用域,也就是全局作用域也持有a)。a是通過普通的詞法作用域查找而非閉包被發(fā)現(xiàn)的。

循環(huán)和閉包

要說明閉包,循環(huán)for是最常見的例子。

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

正常情況下,我們對這段代碼的行為預期是分別輸出數(shù)字1~5,每秒一次,每次一個。

但實際上,這段代碼在運行時會以每秒一次的頻率輸出五次6。

這是為什么?

首先解釋6是從哪里來的。這個循環(huán)的終止條件是i不再小于等于5.條件首次成立時i的值是6.因此,輸出顯示的是循環(huán)結束時i的最終值。

其次要清楚,延遲函數(shù)的回調(diào)會在循環(huán)結束才執(zhí)行。事實上,當給定時器運行時即使每個迭代器中執(zhí)行的是setTimeout(..,0),所有的回調(diào)函數(shù)依然是在循環(huán)結束后才會被執(zhí)行,因此會每次輸出一個6出來。

那為什么沒有獲得我們預期的結果呢,從1~5輸出?

根據(jù)作用域原理,盡管循環(huán)中五個輸出函數(shù)是被分別定義出來的,但是他們都被封閉在一個共享全局作用域下,實際上還是共享一個i。

那好,知道解決方法咯,那就不共享一個i,讓每一次的值都相互獨立,每次都獲得對應的值。

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

這樣,將每一次的i值都傳給另一個變量,保證i的實時更新,就可以正常輸出1~5了!

知道原因了,舉一反三,那是不是也會有其他解決辦法呢?

仔細思考我們前面的解決方案。我們使用IIFE函數(shù)(立即執(zhí)行函數(shù))每次迭代時都創(chuàng)建了一個新的作用域。換句話說,我們每次迭代都產(chǎn)生新的塊作用域。那是不是可以聲明塊作用域避免變量共享的問題呢?

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

很酷是吧?塊作用域和閉包聯(lián)手便可“天下無敵”。

模塊

function CoolModule(){
  var something = "cool";
  var another = [1,2,3];
  function doSomething(){
    console.log(something);
  }
  function doAnother(){
    console.log(another.join("!"));
  }
  return {
    doSomething:doSomething,
    doAnother:doAnother
  }
}
var foo = CoolModule();
foo.doSomething();//cool
foo.doAnother();//1!2!3

這個模式在JavaScript中被稱為模塊,最常見的實現(xiàn)模塊模式的方法通常被稱為模塊暴露。首先,CoolModule()只是一個函數(shù),必須要通過它來創(chuàng)建一個模塊實例。如果不執(zhí)行外部函數(shù),內(nèi)部作用域和閉包都無法被創(chuàng)建。

其次,CoolModule()返回一個用對象字面量語法{key:value,...}來表示的對象。這個返回的對象中含有對內(nèi)部對象而不是內(nèi)部數(shù)據(jù)變量的引用。我們保持內(nèi)部數(shù)據(jù)變量是隱藏且私有的狀態(tài)??梢詫⑦@個對象類型的返回值看作本質(zhì)上模塊的公共API。

doSomething()和doAnother()函數(shù)具有涵蓋模塊實例內(nèi)部作用域的閉包(通過調(diào)用CoolModule()實現(xiàn))。

簡單描述一下,模塊模式的閉包需要具備兩個必要條件。

(1)必須有外部的封閉函數(shù),該函數(shù)必須至少被調(diào)用一次。(每次調(diào)用都會創(chuàng)建一個新的模塊實例)。

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

另外,模塊也是普通的函數(shù),因此可以接受參數(shù):

function CoolModule(id){
  function identify(){
    console.log(id)
  }
  return {
    identify:identify
  }
}
var foo1 = CoolModule("foo1");
var foo2 = CoolModule("foo2");
foo1.identify();//"foo1"
foo2.identify();//"foo2"

小結

閉包就好像從JavaScript中分離出來的一個充滿神秘色彩的未開化世界,只有最勇敢的人才能夠到達那里,但實際上他只是一個標準,顯然就是關于如何在函數(shù)作為值按需傳遞的此法環(huán)境中書寫代碼的。

當函數(shù)可以記住并訪問所在的詞法作用域,即使函數(shù)是在當前詞法作用域之外執(zhí)行,這就產(chǎn)生了閉包。

如果沒能認出閉包,也不了解它的工作原理,在使用它的過程中就很容易犯錯,比如在循環(huán)中。但同時閉包也是一個非常強大的工具,可以用多種形式來實現(xiàn)模塊等模式。

模塊主要有兩個特征:

(1)為創(chuàng)建內(nèi)部工作域而調(diào)用了一個包含函數(shù)。

(2)包裝函數(shù)的返回值必須至少包括一個對內(nèi)部函數(shù)的引用,這樣就會創(chuàng)建涵蓋整個包裝函數(shù)內(nèi)部作用域的閉包。

以上就是一文了解你不知道的JavaScript閉包篇的詳細內(nèi)容,更多關于JavaScript 閉包的資料請關注腳本之家其它相關文章!

相關文章

最新評論