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