1. 程式人生 > 其它 >閉包的原理,優缺點,應用場景,常見面試題總結

閉包的原理,優缺點,應用場景,常見面試題總結

閉包的原理,優缺點,應用場景,常見面試題總結

1.概念

閉包:可以把閉包理解成一個函式,一個父函式裡面巢狀的子函式(也就是函式中的函式),且該子函式必須使用了父函式的變數。

如:

 function f1(){
       var b=2;
       function f2(){
           b++;
           console.log(b);
       }
       return f2;
    };
    var f=f1();
    f();

在上面程式碼中,f1()是父函式,而f2()是f1()的子函式,且f2中使用了父函式中的變數b。在這裡,f2就是閉包。

閉包形成條件:

  1. 必須有一個內嵌函式

  2. 內嵌函式必須引用外部函式中的變數

  3. 外部函式的返回值必須是內嵌函式

2.生命週期

產生:在巢狀的子函式定義執行完成時就產生了

死亡:在巢狀的內部函式成為垃圾物件時

 function f1(){
        //此時就已經產生閉包(因為函式提升)
       var b=2;
       function fn(){
           b++;
           console.log(b);
       }
       return fn;
    };
    var f=f1();
    f();
    f=null//閉包消失,因為內部函式成為了垃圾物件(沒有變數在引用它)

3.優缺點

優點:

(1)可以讓子函式內的變數被訪問

(2)可以讓子函式的變數的值保留在記憶體裡面不被清除

(3)方便呼叫上下文的區域性變數

(4)加強封裝性

缺點:

(1)由於閉包會讓子函式的變數的值保留在記憶體裡面,就容易造成大量記憶體被佔用,記憶體消耗過大,可能會導致記憶體洩漏,影響網頁效能。解決方法:及時清除不使用的區域性變數,也就是賦值null。

(2)閉包會在父函式外部,改變父函式內部變數的值。所以,如果你把父函式當作物件(object)使用,把閉包當作它的公用方法(Public Method),把內部變數當作它的私有屬性(private value),這時一定要小心,不要隨便改變父函式內部變數的值。

4.常見面試題

(1)

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,0,0,0
var b = fun(0).fun(1).fun(2).fun(3);             //undefined,0,1,2
var c = fun(0).fun(1);  c.fun(2);  c.fun(3);     //undefined,0,1,1

第一行:fun(0)即fun(0,o),o並未賦值->undefined, a其實是function()函式,也就是a是一個閉包,a=fun(0)返回的是fun(m,0),所以後面的fun(0); a.fun(1); a.fun(2); a.fun(3)都為0,因為閉包中的n沒有變,都是同一個閉包a。

第二行:

同樣,fun(0)即fun(0,o),o並未賦值->undefined, fun(0).fun(1)->fun(1,0)這時輸出0,fun(0).fun(1).fun(2)->fun(0,1).fun(2)->fun(2,1)這時輸出1,

fun(0).fun(1).fun(2).fun(3)->fun(2,1).fun(3)->fun(3,2),這時輸出2

第三行:由於後面兩個c.fun(2); c.fun(3)都是同一個閉包c在起作用,所以後面都是1

(2)

function f1(){
    var n=999;
    nAdd=function(){n+=1}
    function f2(){
      alert(n);
    }
    return f2;
  }
  var result=f1();
  result(); // 999
  nAdd();
  result(); // 1000

result是f2(),所以呼叫result(),就是執行f2,由於全域性變數n為999,所以這裡就是999。

nAdd()這裡執行了一遍函式,n變成了1000。

再執行一遍result(),此時n已經是1000了

(3)

   function f1() {
        var n = 999;
        nAdd = function () {
          n += 1;
        };
        function f2() {
          alert(n);
        }
        return f2;
      }
      var result1 = f1();
      var result2 = f1();
      result1(); // 999
      result2(); //999
      nAdd();
      result1(); // 是999而不是1000,這是為何呢?
      result2(); //1000

varresult1=f1();

varresult2=f1(); 這兩行呼叫了兩次f1(),相當於分別執行了以下兩段程式碼, 執行result1=f1()時:
 function f1(){
    var n=999;
    //n在result1中的引用為temp1
    var temp1 = n;
    nAdd=function(){
        temp1 += 1;
    };
    function f2(){
      alert(temp1);
    }
    return f2;
  }

執行result2=f1()時:

function f1(){
    var n=999;
     //n在result2中的引用為temp2
    var temp2 = n;
    nAdd=function(){
        temp2 += 1;
    };
    function f2(){
      alert(temp2);
    }
    return f2;
  }

由於result1和result2分別形成了閉包,分別對n進行了儲存,所以順著順序執行nAdd();時,這裡是對result2閉包中的n進行了修改,result1閉包把它自己的n保護起來了。

所以執行完nAdd(),result1()還是999,result2()變成1000。

(4)

function fn(){//建立父函式(爸爸)
   var arr = [];
   for(var i = 0;i < 5;i ++){//這裡迴圈相當於建立了五個子函式(兒子)
	 arr[i] = function(){
		 return i;
	 }
   }
   return arr;
}
var list = fn();//這裡只調用了一次父函式,
for(var i = 0,len = list.length;i < len ; i ++){
   console.log(list[i]());
}  //5 5 5 5 5

參考部落格:

https://blog.csdn.net/yingzizizizizizzz/article/details/72887161

https://blog.csdn.net/weixin_43586120/article/details/89456183

https://blog.csdn.net/yingzizizizizizzz/article/details/77726346

https://blog.csdn.net/u011043843/article/details/46640847?utm_source=app&app_version=4.5.7