你不知道的javascript--上卷--讀書筆記2

來源:互聯網
上載者:User

標籤:函數   其他   包含   function   私人方法   turn   本質   實值型別   javascrip   

  • 閉包是什嗎?

  答:當函數可以記住並訪問所在的詞法範圍時,就產生了閉包,即使函數是在當前詞法範圍之外執行。通俗地來說:函數可以嵌套在其他函數中定義,這樣它們就可以訪問它們被定義時所處的範圍中的任何變數,這就是JavaScript的閉包。

  • 閉包有哪些應用?

  答:函數作為傳回值:

function foo() {var a = 2;function bar() {   //bar擁有涵蓋foo範圍的閉包,並對它保持了引用console.log( a );        }return bar;}var baz = foo();baz(); // 2

  函數作為參數進行傳遞:

function foo() {
var a = 2;
function baz() { //baz擁有涵蓋foo範圍的閉包,並對它保持了引用
console.log( a ); // 2
}
bar( baz );
}
function bar(fn) {
fn();
}
foo();

本質上無論何時何地,如果將函數(訪問它們各自的詞法範圍)當作第一級的實值型別併到處傳遞,你就會看到閉包在這些函數中的應用。在定時器、事件監聽器、 Ajax 請求、跨視窗通訊、Web Workers 或者任何其他的非同步(或者同步)任務中,只要使用了回呼函數,實際上就是在使用閉包!

  • 閉包有哪些作用?

  答:閉包可以訪問函數內部的變數;可以讓這些變數始終保持在記憶體中,即閉包可以使得它誕生環境一直存在;可以封裝對象的私人方法和私人屬性,實現模組化。

  迴圈和閉包

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

結果:輸出5個6;

上述代碼中,我們的預期是分別輸出數字 1~5,每秒一次,每次一個,但實質上卻輸出了5個6;為什麼會這樣?

6是迴圈結束時 i 的最終值,這個不難理解,但為什麼會是5個6呢?根據範圍的工作原理,首先聲明一個i變數,然後進行迭代,每次迭代對i進行LHS操作,接著定義一個延遲1S輸出函數,對i進行RHS操作,看起來好像沒什麼問題,實質上呢?各個迭代的函數共用同一個i的引用,在執行過程中,迴圈快還是延遲輸出快?由結果不難推知,迴圈快。由此,私以為執行過程可以與下述代碼等效:

var i= 6;setTimeout( function timer() {console.log( i );   //6}, i*1000 );setTimeout( function timer() {console.log( i );    //6}, i*1000 );setTimeout( function timer() {console.log( i );   //6}, i*1000 );setTimeout( function timer() {console.log( i );    //6}, i*1000 );setTimeout( function timer() {console.log( i );    //6}, i*1000 );

 

如此看來問題就很簡單了,在迴圈的過程中每個迭代我們都需要一個閉包範圍。

在這之前,我們需要瞭解一些概念:

函式宣告: function aaa(){}  

函式宣告雖然可以實現函數範圍的建立,但由此也帶來了一個問題,就是全域變數的汙染(aaa 被綁定在所在範圍中)和必須顯示的調用這個函數才能執行其中的代碼。那麼怎麼才能同時解決這兩種問題呢?

立即調用函數運算式: (function aaa(){})() ;   或者  (function aaa (){}());

  由於函數被包含在一對 ( ) 括弧內部,因此成為了一個運算式,通過在末尾加上另外一個 ( ) 可以立即執行這個函數,

  區分函式宣告和運算式最簡單的方法是看 function 關鍵字出現在聲明中的位 置(不僅僅是一行代碼,而是整個聲明中的位置)。如果 function 是聲明中 的第一個詞,那麼就是一個函式宣告,否則就是一個函數運算式。

ok,言歸正傳,上面說到,在迴圈的過程中每個迭代我們都需要一個閉包範圍。而立即調用函數運算式會通過聲明並立即執行一個函數來建立範圍。所以我們可以將上述迴圈改寫成這樣:

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

這樣可以嗎?不可以,雖然每次迭代我們都建立了一個新的範圍,但這個範圍是空的,沒有任何變數來儲存迭代中i的值。所以我們還需要聲明一個變數:

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

還有其它方法嗎?有,在塊範圍那裡說過ES6中的:let;let可以將聲明的變數綁定到所在的任意的範圍內,也可以說將一個塊轉換成一個可以被關閉的範圍。比如這樣:

for (var i=1; i<=5; i++) {let j = i; // 是的,閉包的塊範圍!setTimeout( function timer() {console.log( j );}, j*1000 );}或者for (let i=1; i<=5; i++) {setTimeout( function timer() {console.log( i );}, i*1000 );}請記住:for迴圈中let聲明,會在每一次迭代中都聲明變數,且每次迭代都會使用上一次迭代的結束值來初始設定變數。
  • 模組:

  模組的一般形式:建立對象的私人屬性和私人方法,然後通過閉包建立一個能夠訪問對象私人屬性和方法的特權方法,最後返回這個函數或者把它儲存到能夠訪問到的地方。

  模組模式具有兩個必要條件:

  1. 必須有外部的封閉函數,該函數必須至少被調用一次(每次調用都會建立一個新的模組執行個體)。

  2. 封閉函數必須返回至少一個內建函式,這樣內建函式才能在私人範圍中形成閉包,並 且可以訪問或者修改私人的狀態。

  

var foo = (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  };})();foo.doSomething(); // coolfoo.doAnother(); // 1 ! 2 ! 3

 

你不知道的javascript--上卷--讀書筆記2

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.