react hooks的過時閉包


本文主要由淺入深,研究幾個問題

      問題1:閉包是如何形成的?

      1、都知道,閉包的典型特征就是函數里面返回函數,注意閉包不是一種函數,而是一種現象。它的作用是可以讓內部函數訪問到外部函數的變量,而外部無法訪問內部的變量。

function foo(){
var a = 1;
function fn(){
a++;
console.log('inner a',a);
}
console.log('outer a',a)
return fn;
}
foo(); //輸出outer a,1

foo()(); //輸出inner a,2和outer a:1

  2、分析下這段代碼,內部函數fn需要使用變量a,當一個函數被執行時,會創建一個執行上下文(代碼的執行環境),而a這個變量的聲明並不在fn的執行環境中,所以只能在fn的詞法作用域中去尋找a,此時我們可以理解為fn的詞法作用域就是foo的作用域。

             所以這邊存在了一個現象:變量a是在foo的執行上下文中被創建的,但是fn卻包裹着被改變量的引用,只要變量存在着引用,那么這個變量是無法被釋放的,這就形成了閉包。

       3、順着上面的代碼,再度執行foo()和foo()();

function foo(){
  var a = 1;
  function fn(){
    a++;
    console.log('inner a',a); 
  }
  console.log('outer a',a)     
  return fn;
}
foo();      //輸出outer a,1
foo()();   //輸出inner a,2和outer a:1
foo();     //輸出outer a,1
foo()();  //輸出outer a,1

     我們發現,打印出來的結果跟上次的完全一樣。這很好解釋,每次函數執行時,會創建一個執行上下文,這個執行上下文會被推到執行棧中,並且瀏覽器會給每個變量分配內存。執行完畢以后,內存回收,執行上下文出棧,下次執行繼續重復上面的的過程。

          變量a正常被GC回收。

      4、什么時候變量a無法被回收呢?將代碼稍微調整下,此處不研究outer a了,故將其去掉。

function foo(){
  var a = 1;
  function fn(){
    a++;
    console.log('inner a',a); 
  }
  return fn;
}
var f = foo();
f();   //inner a,2
f();   //inner a,3
f();   //inner a,4

  發現每執行一次f,變量a就會+1,仿佛被記憶了一樣,這又是為何呢?跟上面的區別主要是在於一個是直接執行,一個是將函數賦值個一個變量來執行。為什么結果不同?

       此處我給的解釋是:var f = foo(); 這邊的 f 實際上是一個全局變量,而在js變量回收規則里面,全局變量是不會被回收的。同時var f = foo()實際上創建了一個內部函數fn的引用,因此它可以訪問到fn執行時所在的代碼環境,而a一直存在於這個

       環境里,就有了不被回收的理由。

   問題2:來看看hooks里面過時的閉包

function create(i){
   let value = 0;
   return function(){
       value += i;
       console.log('value',value)
       const message = `The current value is ${value}`;
        return function logValue(){
        console.log(message);
      }
   }
};
var inc = create(1);
var log = inc();   // value 1
inc();             // value 2
inc();             // value 3
log();             // The curent value is 1

  可以看到異常了,inc執行了三次以后,value應該是3,但是最后執行log的時候打印出來的message顯示的value卻是1,此處成log為過時的閉包,message為過時的變量。OK,那value到底是多少呢?繼續在logValue里面將value打印出來。

function create(i){
   let value = 0;
   return function(){
       value += i;
       console.log('value',value)
       const message = `The current value is ${value}`;
        return function logValue(){
        console.log(value,message);
      }
   }
};
var inc = create(1);
var log = inc();      // value 1
inc();                // value 2
inc();                // value 3
log();                // 3 The curent value is 1

  正應了上面所述,value並沒有過時,過時的變量是message。

       為什么message是過時的變量呢?對這段代碼進行分析:

       1)var inc = create(1);創建了一個變量inc,該變量返回的是create函數里面第一層的匿名函數的引用。匿名函數被執行時,創建了執行上下文,而value一直存在與這個執行上下文中。但message也在啊,為什么會過期呢?

       2)value跟message雖然都存在於同一個執行上下文里面,但是它們的生命周期不一樣,value不是在當前執行上下文被創建的,而是在其詞法作用域即外部的create()函數里面被創建的,根據閉包形成的條件,變量value不被釋放。

            而message就不同了,每次調用這個匿名函數的時候,message都會被執行上下文重新創建,重新分配內存。

       3)var log = inc();創建了一個變量log,該變量返回的是執行了一次inc以后的logValue函數的引用,此時value=1.后面inc()被調用了多次,value發生了變化.

       4) 最后執行log()的時候,即執行了logValue(),此時的message不在當前執行上下文中,要去其詞法作用域中尋找,而message所在的詞法作用域即第一層匿名函數,早先賦值給了log,所以輸出了一個過時的結果。

問題3:如何解決過時的閉包問題?

       原理理解了就好辦了,因為我們發現value並沒有過時,所以調整下message的位置即可。

function create(i){
   let value = 0;
   return function(){
       value += i;
       console.log('value',value)
       return function logValue(){
         const message = `The current value is ${value}`;
        console.log(value,message);
      }
   }
};
var inc = create(1);
var log = inc();      // value 1
inc();                // value 2
inc();                // value 3
log();                // 3 The curent value is 3

  或者改變下var log = inc()的順序,使用新的閉包。此處略。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM