[JS]10.闭包应用(循环处理)

366 阅读2分钟
  • 基于闭包的机制完成

  • 函数执行形成私有上下文,执行完成后,当前上下文有东西被当前上下文以外的东西占用,不能被释放,就是闭包

  • 闭包的特点

    • 闭包的保护机制:保护当前上下文的变量独立,不受外接干扰
    • 闭包的保存机制:保存起来私有变量,形成不释放的上下文呢,供下级上下文使用
  • 闭包的弊端:闭包占用内存,消耗浏览器的性能(不能滥用闭包)

1. 循环事件绑定或循环操作中对闭包的应用

1.1 循环事件普通输出

  • setTimeout([function], [interval]) 设置一个定时器,等待interval时长,触发function执行
for (var i = 0; i < 3; i++) {
  setTimeout(()=>{
    console.log(i)
  }, (i+1)*1000)
}
  • 第一轮循环i=0,设置创建一个定时器,此时并未执行function,1000ms后执行
  • 第二轮循环i=1,设置创建一个定时器,此时并未执行function,2000ms后执行
  • 第三轮循环i=2,设置创建一个定时器,此时并未执行function,3000ms后执行
  • 此时同步代码for循环执行到 i=3 循环结束
  • 1000ms到了执行回调,在形成的私有上下文中无i,找上级上下文,也就是全局i = 3
  • ... 输出3个3

1.2 循环事件闭包改造

  • 思路,在自己上下文中保存一个i,闭包思路

1.2.1 写法1

for (var i = 0; i < 3; i++) {
  // 每轮循环,自执行函数执行,都会产生一个私有上下文
  // 并且是把当前这一轮循环,全局变量i的值作为实参传递给私有上下文中的形参i
  (function(i){
     setTimeout(()=>{
       console.log(i)
     }, (i+1)*1000)
  })(i);
}
  • 第一轮EC(AN1) 形参赋值 i = 0
  • 第一轮EC(AN2) 形参赋值 i = 1
  • 第一轮EC(AN3) 形参赋值 i = 2
  • 每一个形成的私有上下文中,都会创建一个箭头函数堆,并且把值赋值给了window.setTimeout,这样等价于当前上下文中的某些内容被上下文以外的内容占用了,形成的上下文不会被释放,私有变量i的值也不会被释放,闭包,循环三次形成三个闭包

1.2.2 写法2


// let xxx = proxy(0);  // -> proxy执行会返回一个函数,被xxx占用,不释放,闭包,闭包中私有变量,就是函数执行传递的参数

const proxy = i => {
  return () => {
    console.log(i);
  };
}
for (var i = 0; i < 3; i++) {
  // 回调执行的是proxy返回的小函数
  setTimeout(proxy(i), (i+1)*1000)
}

1.2.3 写法3

  • let会形成私有的块级作用域
  • 基于let的循环,let存在块级作用域的处理机制,首先浏览器会创建一个父级私有上下文,来控制循环
  • 每一轮循环还会产生一个私有的块级上下文,都有自己的私有变量i,存储当前这一轮循环i的值
  • 每一个私有块级上下文中,也是创建一个函数,并且被window.setTimeout给占用了,不会释放这个块级上下文,闭包
for (let i = 0; i < 3; i++) {
  setTimeout(()=>{
    console.log(i)
  }, (i+1)*1000)
}
  • let形成的闭包,是浏览器底层实现的,从性能上比自己创造的闭包要快一点
for (let i = 0; i < 3; i++) {
   // 每一轮循环都会产生一个私有的块级上下文,如果上下文中没有什么东西被外部占用,则本轮循环结束,块级上下文被释放
   // 一旦有东西被占用,则会产生闭包
  setTimeout(()=>{
     // 创建函数作用域就是上下文,与x无关,也形成闭包
     // 与i无关的代码,setTimeout被占用,也是闭包
  }, 1000)
}
  • 不需要i推荐写法,这种情况,不会产生块级上下文,不形成闭包
let i = 0;
for(; i<3; i++) {
  setTimeout(()=>{
   ...
  }, 1000)
}