學習閉包

1. 閉包的定義

外層函數嵌套內層函數, 內層函數使用外層函數的局部變量,把內層函數做爲外層函數的返回值。面試

function A() {
   let a = 1
   function B() {
      console.log(a)
  }
  return B
 }
複製代碼

2.閉包的應用

用閉包解決遞歸問題閉包

function  factorial(num) {
    if(num<= 1) {
        return 1;
    } else {
       return num * factorial(num-1)
    }
 }
 var anotherFactorial = factorial
 factorial = null
 anotherFactorial(4)   // 報錯 。
 //最好是return num* arguments.callee(num-1),arguments.callee指向當前執行函數,可是在嚴格模式下不能使用該屬性也會報錯,因此藉助閉包來實現

 // 使用閉包實現遞歸
 function newFactorial = (function f(num){
     if(num<1) {return 1}
     else {
        return num* f(num-1)
     }
 }) 
 //這樣就沒有問題了,實際上起做用的是閉包函數f,而不是外面的函數newFactorial
複製代碼

用閉包模仿塊級做用域異步

  • 例1:函數

    for(var i=0; i<10; i++){
         console.log(i)
    }
    alert(i)  // 變量提高,彈出10
    
    //爲了不i的提高能夠這樣作
    (function () {
        for(var i=0; i<10; i++){
             console.log(i)
        }
    )()
    alert(i)   // undefined   由於i隨着閉包函數的退出,執行環境銷燬,變量回收
    複製代碼
  • 例2:性能

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

封裝私有變量學習

function create_counter(initial) {
        var x = initial || 0;
        return {
            inc: function () {
                x += 1;
                return x;
            }
        }
   }
   var c1 = create_counter();
   c1.inc(); // 1
   c1.inc(); // 2
   c1.inc(); // 3

   var c2 = create_counter(10);
   c2.inc(); // 11
   c2.inc(); // 12
   c2.inc(); // 13
複製代碼

在返回的對象中,實現了一個閉包,該閉包攜帶了局部變量x,而且,從外部代碼根本沒法訪問到變量x。換句話說,閉包就是攜帶狀態的函數,而且它的狀態能夠徹底對外隱藏起來。this

3.閉包的做用

  • 讀取函數內部變量spa

  • 讓變量的值始終保持在內存中code

4.閉包的注意事項

一般,函數的做用域及其全部變量都會在函數執行結束後被銷燬,被垃圾回收機制回收。可是,在建立了一個閉包之後,這個函數的做用域就會一直保存到閉包不存在爲止。對象

function makeAdd(x) {
    return function(y) {
      return x + y;
    };
  }

  var add1 = makeAdder(5);
  var add2 = makeAdder(10);

  console.log(add1(4));  // 9
  console.log(add2(3)); // 13

  // 釋放對閉包的引用
  add5 = null;
  add10 = null;
複製代碼

閉包只能取得包含函數中任何變量的最後一個值,這是由於閉包所保存的是整個變量對象,而不是某個特殊的變量。

function test(){
    var arr = [];
    for(var i = 0;i < 10;i++){
      arr[i] = function(){
        return i;
       };
    }
    for(var a = 0;a < 10;a++){
       console.log(arr[a]());
    }
  }
  test(); // 連續打印 10 個 10
複製代碼

閉包中的this

var name = "The Window";
 var obj = {
   name: "My Object",
   getName: function(){
       var that = this;
       return function(){
          return that.name;
      };
    }
 };
 console.log(obj.getName()());  // The Window
 //將這一部分解:console.log( function(){return this.name;};() ); 
複製代碼

改變做用域

var name = "The Window";
 var obj = {
   name: "My Object",
   getName: function(){
       var that = this;
       return function(){
          return that.name;
      };
    }
 };
 console.log(obj.getName()());  // The Window
 //將這一部分解:console.log( function(){return this.name;};() ); 
複製代碼

5.閉包的缺點

  • 閉包的缺點就是常駐內存會增大內存使用量,而且使用不當很容易形成內存泄露。

  • 若是不是由於某些特殊任務而須要閉包,在沒有必要的狀況下,在其它函數中建立函數是不明智的,由於閉包對腳本性能具備負面影響,包括處理速度和內存消耗。

6.關於閉包的面試題

這些面試題很是有意思,堅持一下,來我們繼續往下看>_<

第一個JS閉包問題

function fun(n,o) {
  console.log(o)
  return {
    fun:function(m){
      return fun(m,n);
    }
  };
}
var a = fun(0);  a.fun(1);  a.fun(2);  a.fun(3);//undefined,?,?,?
var b = fun(0).fun(1).fun(2).fun(3);//undefined,?,?,?
var c = fun(0).fun(1);  c.fun(2);  c.fun(3);//undefined,?,?,?
//問:三行a,b,c的輸出分別是什麼?

//答案:
//a: undefined,0,0,0
//b: undefined,0,1,2
//c: undefined,0,1,1
複製代碼

(1)先肯定這三個函數的關係

這段代碼中出現了三個fun函數,因此第一步先搞清楚,這三個fun函數的關係,哪一個函數與哪一個函數是相同的。

function fun(n,o) {
  console.log(o)
  return {
    fun:function(m){
      //...
    }
  };
}
複製代碼

先看第一個fun函數,屬於標準具名函數聲明,是新建立的函數,他的返回值是一個對象字面量表達式,屬於一個新的object。這個新的對象內部包含一個也叫fun的屬性,經過上述介紹可得知,屬於匿名函數表達式,即fun這個屬性中存放的是一個新建立匿名函數表達式。

注意:全部聲明的匿名函數都是一個新函數。

因此第一個fun函數與第二個fun函數不相同,均爲新建立的函數。最內層的return出去的fun函數不是第二層fun函數,是最外層的fun函數。因此,三個fun函數的關係也理清楚了,第一個等於第三個,他們都不等於第二個。

(2)函數是怎樣調用的

爲了方便看把代碼從新寫一下

function fun(n,o) {
  console.log(o)
  return {
    fun:function(m){
      return fun(m,n);
    }
  };
}
var a = fun(0);  a.fun(1);  a.fun(2);  a.fun(3);//undefined,?,?,?
var b = fun(0).fun(1).fun(2).fun(3);//undefined,?,?,?
var c = fun(0).fun(1);  c.fun(2);  c.fun(3);//undefined,?,?,?
//問:三行a,b,c的輸出分別是什麼?
複製代碼

第一行 a

var a = fun(0);  a.fun(1);  a.fun(2);  a.fun(3);
複製代碼

第一個fun(0)是在調用第一層fun函數。第二個fun(1)是在調用前一個fun的返回值的fun函數,因此:第後面幾個fun(1),fun(2),fun(3),函數都是在調用第二層fun函數。

遂:

  • 在第一次調用fun(0)時,o爲undefined;

  • 第二次調用fun(1)時m爲1,此時fun閉包了外層函數的n,也就是第一次調用的n=0,即m=1,n=0,並在內部調用第一層fun函數fun(1,0);因此o爲0;

  • 第三次調用fun(2)時m爲2,但依然是調用a.fun,因此仍是閉包了第一次調用時的n,因此內部調用第一層的fun(2,0);因此o爲0。

  • 第四次同理;

即:最終答案爲 undefined,0,0,0

第二行 b

var b = fun(0).fun(1).fun(2).fun(3);//undefined,?,?,?
複製代碼

先從fun(0)開始看,確定是調用的第一層fun函數;而他的返回值是一個對象,因此第二個fun(1)調用的是第二層fun函數,後面幾個也是調用的第二層fun函數。

遂:

  • 在第一次調用第一層fun(0)時,o爲undefined;

  • 第二次調用 .fun(1)時m爲1,此時fun閉包了外層函數的n,也就是第一次調用的n=0,即m=1,n=0,並在內部調用第一層fun函數fun(1,0);因此o爲0;

  • 第三次調用 .fun(2)時m爲2,此時當前的fun函數不是第一次執行的返回對象,而是第二次執行的返回對象。而在第二次執行第一層fun函數時時(1,0)因此n=1,o=0,返回時閉包了第二次的n,遂在第三次調用第三層fun函數時m=2,n=1,即調用第一層fun函數fun(2,1),因此o爲1;

  • 第四次調用 .fun(3)時m爲3,閉包了第三次調用的n,同理,最終調用第一層fun函數爲fun(3,2);因此o爲2;

即最終答案:undefined,0,1,2

第三行 c

var c = fun(0).fun(1);  c.fun(2);  c.fun(3);//undefined,?,?,?
複製代碼

根據前面兩個例子,能夠得知:

fun(0)爲執行第一層fun函數,.fun(1)執行的是fun(0)返回的第二層fun函數,這裏語句結束,遂c存放的是fun(1)的返回值,而不是fun(0)的返回值,因此c中閉包的也是fun(1)第二次執行的n的值。c.fun(2)執行的是fun(1)返回的第二層fun函數,c.fun(3)執行的也是fun(1)返回的第二層fun函數。

遂:

  • 在第一次調用第一層fun(0)時,o爲undefined;

  • 第二次調用 .fun(1)時m爲1,此時fun閉包了外層函數的n,也就是第一次調用的n=0,即m=1,n=0,並在內部調用第一層fun函數fun(1,0);因此o爲0;

  • 第三次調用 .fun(2)時m爲2,此時fun閉包的是第二次調用的n=1,即m=2,n=1,並在內部調用第一層fun函數fun(2,1);因此o爲1;

  • 第四次.fun(3)時同理,但依然是調用的第二次的返回值,遂最終調用第一層fun函數fun(3,1),因此o還爲1

即最終答案:undefined,0,1,1

第二個JS閉包問題

循環中使用閉包解決 var 定義函數的問題

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

首先由於 setTimeout 是個異步函數,全部會先把循環所有執行完畢,這時候 i就是 6 了,因此會輸出一堆 6。

解決辦法兩種,第一種使用閉包

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

第二種就是使用 setTimeout 的第三個參數

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

第三種就是使用 let 定義 i 了

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

由於對於 let 來講,他會建立一個塊級做用域,至關於

{ // 造成塊級做用域
  let i = 0
  {
    let ii = i
    setTimeout( function timer() {
        console.log( ii );
    }, i*1000 );
  }
  i++
  {
    let ii = i
  }
  i++
  {
    let ii = i
  }
  ...
}
複製代碼

若是您有更好的建議,或者對該篇文章的知識補充,請留言,實踐後會及時補充的哦,謝謝>-<!

該篇文章多方參考,學習筆記。

相關文章
相關標籤/搜索