常见的闭包代码陷阱

闭包深入理解

闭包指的是能够访问另一个函数作用域中变量的函数
清晰的讲:闭包就是一个函数,这个函数能够访问其他函数的作用域中的变量

  function f(x=0){
       var count=x;
       function getCount(){
           return count++;
       }
       return getCount
   }
   var func = f(10);
   console.log(func());    // 10

代码如下

  let f = ()=>{
      let func = []
      for(var i = 0; i < 3; i++){
          func.push(()=>{
              return i*i;
          })
      }
      return func 
  }

这样,f1应该输出0, f2输出1,f3输出4 ……,本应该这样才对。但是结果却是三个9。

  let [f1,f2,f3]=f();
  console.log(f1());  // 9
  console.log(f2());  // 9
  console.log(f3());  // 9

原因分析

道理很简单,因为使用了var声明的变量。var声明的变量是没有块级作用域的,上面的代码在逻辑上,等价于

  let f = ()=>{
      let func = []
      var i
      for(i = 0; i < 3; i++){
          func.push(()=>{
              return i*i;
          })
      }
      return func
  }

所以解析顺序就是:
1,因为 func[0] / func[1] / func[2]三个闭包函数的外部词法环境都是循环体的词法环境和函数f的词法环境:
2,在查询标识符i的过程中, 在循环体的词法环境中未找到标识符i,所以将使用函数f的词法环境中的标识符i。
3,调用f1 / f2 / f3时,循环体已经结束, 所以 标识符 i最终值为3。所以最终皆返回 9

我们知道,导致最终结果都相同的原因是f1/f2/f3在解析标识符i的过程中使用的都是 函数词法环境 的标识符i

解决方法

方法一 在返回的函数外部创建一个立即执行函数表达式,在这个函数中定义一个新的标识符x

 let f = ()=>{
     let func = []
     for(var i = 0; i < 3; i++){
         func.push(   
            (function(){
	             var x=i;
	             return ()=>x*x;
            })() 
         )
     }
     return func 
 }
  let [f1,f2,f3]=f();
  console.log(f1());    // 0
  console.log(f2());    //1
  console.log(f3());   //4

方法二 在循环体内创建let变量来接收值,使用块作用域

  let f = ()=>{
      let func = []
      for(var i = 0; i < 3; i++){
          let x = i;         //let 定义变量,接收即将要返回的值
          func.push(()=>{
              return x*x;
          })
      }
      return func 
  }
  let [f1,f2,f3]=f();
  console.log(f1());    // 0
  console.log(f2());    //1
  console.log(f3());   //4

方法三 循环体定义i时,用let定义,使用块作用域(推荐!推荐!推荐!)

  let f = ()=>{
      let func = []
      for(let i = 0; i < 3; i++){
          func.push(()=>{
              return i*i;
          })
      }
      return func 
  }
  let [f1,f2,f3]=f();
  console.log(f1());    // 0
  console.log(f2());    //1
  console.log(f3());   //4

有个经典面试题,是这样的:

for(var i =0;i<6;i++){
     setTimeout(()=>{console.log(i)},0)         //输出666666
}

方法一个 利用setTimeout第三个参数这样改下

for(var i =0;i<6;i++){
   setTimeout((j)=>{console.log(j)},0,i);    //正常输出 0 1 2 3 4 5
}

它是正常输出的, 因为setTimeout接收了当前i的值作为参数后,在函数内部将i参数传递给延时执行的函数

参考:https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout

方法二 使用let ,块作用域

for(let i =0;i<6;i++){
   setTimeout(()=>{console.log(i)},0);
}

方法三 在循环体内创建let变量来接收值,使用块作用域

for(var i =0;i<6;i++){
  let j = i;  	
         setTimeout(()=>{console.log(j)},0);
}

版权声明:本文为CSDN博主「krfwill」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/krfwill/article/details/106290329

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值