《你不知道的javascript(上)》作用域與閉包(三)

在翻閱《你不知道的javascript》這一套書的中上卷目錄之后,發(fā)現(xiàn)書中針對(duì)閉包、對(duì)象、原型、語(yǔ)法、異步、回調(diào)等等既基礎(chǔ)又重要的
javascript知識(shí)有著針對(duì)性的闡述,于是決定對(duì)這套書的中上卷進(jìn)行學(xué)習(xí)。上卷和中卷各講述了兩大部分知識(shí),分別是:作用域與閉包、
this和對(duì)象原型、類型和語(yǔ)法、異步和性能。本文是對(duì)作用域與閉包的學(xué)習(xí)總結(jié)。

閉包,一個(gè)非常神秘的詞語(yǔ),其實(shí)不光js中存在閉包的概念,在其他編程語(yǔ)言中也有。在《你不知道的javascript(上)》一書中,通過一些易懂的實(shí)例,很容易的就解釋清楚閉包,以及它的主要作用。學(xué)習(xí)之后,我認(rèn)為對(duì)閉包主要掌握三個(gè)要點(diǎn):概念,實(shí)際用途,利用閉包來(lái)建立模塊。

1.閉包概念

  正如書中所說(shuō),閉包在js中無(wú)處不在,其定義也直截了當(dāng):當(dāng)函數(shù)可以記住并訪問所在的詞法作用域時(shí),就產(chǎn)生了閉包,即使函數(shù)是在當(dāng)前詞法作用域之外執(zhí)行。注意重點(diǎn),第一句話闡述閉包的定義,根據(jù)js引擎按照作用域鏈向上查找的機(jī)制,函數(shù)內(nèi)部嵌套的函數(shù)肯定會(huì)形成閉包,但是使一個(gè)閉包顯得有意義的卻是第二句話(當(dāng)然閉包不只這一種作用)。

  如下代碼:

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

在foo()函數(shù)中,bar()已經(jīng)形成了一個(gè)閉包,通過return bar將bar返回給了全局作用域,而通過var聲明將bar傳遞給了baz,于是,此時(shí)baz作為在全局作用域中的標(biāo)識(shí)符,卻能夠訪問foo作用域內(nèi)的變量a,這就是閉包所起的作用。

  再來(lái)看一個(gè)復(fù)雜的傳參例子:

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

在foo()函數(shù)中通過bar(baz)調(diào)用了外部函數(shù)bar(fn),也就是將baz傳給了外部函數(shù)bar(fn),既f(wàn)n=baz,因此在執(zhí)行的時(shí)候,執(zhí)行fn()也就是執(zhí)行了一次baz(),于是,作為另一個(gè)函數(shù)中的fn(),就能夠訪問到foo()中的變量a,而這是baz()閉包所起的作用。

2.閉包的作用

  閉包所起的作用不只是能夠在外部作用域中訪問內(nèi)部變量,在回調(diào)函數(shù)及循環(huán)語(yǔ)句中,主要也是閉包在起作用。同樣,看一個(gè)書中提供的使用延時(shí)器的例子:

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

在調(diào)用wait("Hello,closure!")之后,延時(shí)器中的函數(shù)要在1000毫秒后執(zhí)行,此時(shí),按照正常的js垃圾收回機(jī)制,wait()的作用域及其中儲(chǔ)存的變量值"Hello,closure!"應(yīng)該在wait("Hello,closure!")執(zhí)行幾微秒后就被收回了,然后正是由于延時(shí)器中的函數(shù)timer()處于wait(message)函數(shù)內(nèi)部,形成了閉包,使得timer()可以在1000毫秒后能夠訪問到變量值"Hello,closure!"??梢姡]包不僅能使外部訪問函數(shù)作用域內(nèi)的變量,還能使得函數(shù)作用域在一定時(shí)間內(nèi)保持完整。

  有時(shí)候(或者說(shuō)經(jīng)常),會(huì)在for循環(huán)內(nèi)執(zhí)行延時(shí)器,如果延時(shí)器要使用for循環(huán)的i變量(j或者其他也可以,總之就是循環(huán)按照其變化執(zhí)行的那個(gè)變量),經(jīng)常會(huì)出現(xiàn)所有延時(shí)器使用的都是最后一個(gè)i值的情況出現(xiàn)。對(duì)此,需要使用閉包和IIFE才能夠很好的處理這個(gè)問題(IIFE指立即執(zhí)行函數(shù)表達(dá)式)。
如下代碼:

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

預(yù)想的情況是,每隔1000秒執(zhí)行一次timer(),并且timer()中使用的i值依次為1到5。然而實(shí)際情況卻是在6000毫秒之后同時(shí)執(zhí)行了timer(),并且timer()中使用到的i值全都是6。究其原因,需要解釋兩部分內(nèi)容:

  首先是出現(xiàn)i值為6的原因,由于當(dāng)i值為5時(shí),依然滿足for循環(huán)的條件,所以循環(huán)語(yǔ)句會(huì)再執(zhí)行一次,而i++就使得最后的i值為6;其次,為何不是使用的1到5,因?yàn)閒or循環(huán)執(zhí)行完畢只是幾微秒的時(shí)間,而按照預(yù)想最早執(zhí)行的timer()也會(huì)在1000毫秒之后執(zhí)行,此時(shí)i全都變成了6,所以最后延時(shí)器中的函數(shù)全都使用的是6。

  要解決這個(gè)問題,主要方法就是在延時(shí)器外邊包裹一層IIFE,在每次for循環(huán)執(zhí)行的時(shí)候,立即執(zhí)行一次IIFE并將此時(shí)的i值保存在IIFE中,以給IIFE中的延時(shí)器使用。如下代碼:

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

利用j適時(shí)保存了i值,而同時(shí)延時(shí)器是異步的(也就是說(shuō)有5個(gè)延時(shí)器在執(zhí)行),每個(gè)延時(shí)器內(nèi)部的timer()都擁有一個(gè)作用域,此時(shí)體現(xiàn)出了閉包的作用,閉包使得包裹延時(shí)器的IIFE能夠保持完整,j值得以保存,因此最后timer()使用的j變量分別是1到5。此外,利用IIFE的進(jìn)階用法,還可以省掉var j=i語(yǔ)句,如下:

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

直接進(jìn)行了傳參。

3.模塊化

  前端發(fā)展到現(xiàn)今,模塊化已經(jīng)是一個(gè)主流的概念,而如今的模塊主要都是定義一個(gè)模塊封裝函數(shù),使用戶可以自定義模塊。書中介紹了模塊封裝函數(shù)的核心概念,如下代碼:(非常重要的一段代碼,完全可以使用到自己以后的代碼中,自行建模

var MyModules = (function Manager(){
  var modules = {};
  function define(name,deps,impl){
    for(var i=0;i<deps.length;i++){
      dep[i] = modules[deps[i]];
    }
    modules[name] = impl.apply(impl,deps);
  }
  function get(name){
    return modules[name];
  }
  return{
    define: define,
    get:get
  };
})();

簡(jiǎn)單介紹一下我理解的各段代碼的作用:return()返回了MyModules的對(duì)象字面量(此處使用了閉包機(jī)制),以便外部可以使用MyModules的define和get兩個(gè)函數(shù)。define(name,deps,impl)用于自定義模塊(就是在該模塊內(nèi)自定義自己需要定義的函數(shù)),name是函數(shù)名(使用define()后成為MyModules對(duì)象的屬性),deps是自定義函數(shù)需要使用的參數(shù)組,而impl是定義的函數(shù)的具體代碼。
get(name)用于從模塊中取得自己想要使用的函數(shù)或方法。

  以上,關(guān)于模塊的具體使用,在書中有實(shí)例講解了具體如何運(yùn)用,建議看書進(jìn)行了解。

Small Star's Blog|小星的博客

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容