淺談閉包

閉包的定義

閉包就是可以讀取其餘函數內部變量的函數。javascript

例如在javascript中,只有函數內部的子函數才能讀取局部變量,因此閉包能夠理解成「定義在一個函數內部的函數「。java

在本質上,閉包是將函數內部和函數外部鏈接起來的橋樑。segmentfault

爲何要使用閉包

先介紹一下全局變量和局部變量的優缺點bash

全局變量:能夠重用、可是會形成全局污染並且容易被篡改。閉包

局部變量:僅函數內使用不會形成全局污染也不會被篡改、不能夠重用。異步

因此,全局變量和局部變量的優缺點恰好相對。閉包的出現正好結合了全局變量和局部變量的優勢。函數

什麼時候使用閉包

但願重用一個對象,可是又保護對象不被污染篡改時。ui

閉包產生的緣由

官方解釋:spa

閉包是一個擁有許多變量和綁定了這些變量的環境的表達式(一般是一個函數),於是這些變量也是該表達式的一部分。code

從這裏能夠看出閉包與環境有關,而與環境扯上關係就離不開做用域,然而JS做用域中特殊的就是詞法做用域,這個詞法做用域又稱之爲靜態做用域或者閉包。

靜態做用域就是函數聲明時,就已經定好的做用域,之後也不會改變的做用域。

而JS的語言特性在JS代碼運行的時候就已經把一切都定死了,做用域什麼的都j定好了,閉包也隨之而產生。

閉包是JS語言的一種特性,閉包一般是一個函數,函數是一個獨立的做用域,獨利的做用域外部環境沒法訪問,就是;封閉本身的詞法做用域,函數有許多特殊形式的函數,這就成就了。包的東西不一樣,可是它可以引用到外部函數的成員變量,必定是它包的東西。

能夠理解爲,可以引用外部函數的成員變量,那它就必定是閉包。

這裏有個閉包的詳細圖解

閉包的表現形式

  • 返回一個函數
  • 做爲函數參數傳遞
  • 回調函數
  • 非典型閉包IIFE(當即執行函數表達式)

返回一個函數:這種形式的閉包在JS中很是很是常見。

var a  = 1;
function foo(){
  var a = 2;
  // 這就是閉包
  return function(){
    console.log(a);
  }
}
var bar = foo();
// 輸出2,而不是1
bar();
複製代碼

做爲函數參數傳遞:不管經過何種手段將內部函數傳遞到它所在詞法做用域以外,它都會持有對原始做用域的引用,不管在何處執行這個函數,都會產生閉包。

var a=1;
function foo(){
    var a=2;
    function baz(){
        console.log(a);
    }
    bar(baz);
}
function bar(fn){
    //這就是閉包
    fn();
}
//輸出2,而不是1
foo();
複製代碼

回調函數:在定時器、事件監聽、Ajax請求、跨窗口通訊、Web Workers或者任何異步中,只要使用了回調函數,實際上就是在使用閉包

//定時器
setTimeout(function timeHandler(){
    console.log('timer');
},100)

//事件監聽
$('#container').click(function(){
    console.log('DOM Listener');
})
複製代碼

IIFE:IIFE(當即執行函數表達式)並非一個典型的閉包,但它確實建立了一個閉包。

var a = 2;
(function IIFE(){
  // 輸出2
  console.log(a);
})();
複製代碼

如何解決下面的循環輸出問題

for(var i = 1; i <= 5; i ++){
  setTimeout(function timer(){
    console.log(i)
  }, 0)
}
//爲何會所有輸出6?如何改進,讓它輸出1,2,3,4,5?(方法越多越好)
複製代碼

代碼分析

for循環建立了5個定時器,而且定時器是在循環結束後纔開始執行

for循環結束後,用var i定義的變量i此時等於6

依次執行五個定時器,都打印變量i,因此結果是打印5次6

**第一種改進方法:**利用IIFE(當即執行函數表達式)當每次for循環時,把此時的i變量傳遞到定時器中

for(var i=1;i<=5;i++){
  (function(j){
    setTimeout(function timer(){
      console.log(j)
    },0)
  })(i)
}
複製代碼

第二種改進方法:setTimeout函數的第三個參數,能夠做爲定時器執行時的變量進行使用

for(var i=1;i<=5;i++){
  setTimeout(function timer(j){
    console.log(j)
  }, 0, i)
}
複製代碼

第三種改進方法(推薦):在循環中使用let i代替var i

for(let i=1;i<=5;i++){
  setTimeout(function timer(){
    console.log(i)
  }, 0)
}
複製代碼
相關文章
相關標籤/搜索