Silencer-1984 / Summary-of-front-end-work

总结包括一切关于前端学习的东西,面试、学英语、开发规范
0 stars 0 forks source link

闭包 #3

Open Silencer-1984 opened 3 years ago

Silencer-1984 commented 3 years ago

闭包是什么? 函数 A 内部有一个函数 B,函数 B 可以访问到函数 A 中的变量,那么函数 B 就是闭包,包就是能够读取其他函数内部变量的函数。因为js特有的链式作用域,我们无法读取私有变量所以需要闭包。

闭包是一个函数, 其可以记住并访问外部变量. 在函数被创建时, 函数的隐藏属性 [[Environment]] 会记住函数被创建时的位置, 即当时的词法环境 Lexical Environment 这样, 无论在哪里调用函数, 都会去到 [[Environment]] 所引用的词法环境 当查找变量时, 先在词法环境内部查找, 当没有找到局部变量时, 前往当前词法环境所记录的外部词法环境查找 我知道的闭包的应用: 封装私有变量和处理回调函数 它的最大用处有两个,一个是它可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中

闭包使用场景?

闭包是什么? 正常的函数内部变量在函数执行结束之后就会被内存回收,由于在JS中,变量的作用域属于函数作用域,在函数执行后作用域就会被清理、内存也随之回收,但是由于闭包是建立在一个函数内部的子函数,由于其可访问上级作用域的原因,即使上级函数执行完,作用域也不会随之销毁,这时的子函数——也就是闭包,便拥有了访问上级作用域中的变量的权限,即使上级函数执行完后作用域内的值也不会被销毁。

闭包的作用? 由于闭包可以缓存上级作用域,那么就使得函数外部打破了“函数作用域”的束缚,可以访问函数内部的变量。以平时使用的Ajax成功回调为例,这里其实就是个闭包,由于上述的特性,回调就拥有了整个上级作用域的访问和操作能力,提高了极大的便利。开发者不用去写钩子函数来操作上级函数作用域内部的变量了。

使用闭包的注意点 1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。 2)闭包内部是可以缓存上级作用域,而如果闭包又是异步执行的话,一定要清楚上级作用域都发生了什么,而这样就需要对代码的运行逻辑和JS运行机制相当了解才能弄明白究竟发生了什么。代码不易维护。

经典面试题,循环中使用闭包解决 var 定义函数的问题

for (var i = 1; i <= 5; i++) {
  setTimeout(function timer() {
    console.log(i)
  }, i * 1000)
}

解题

for (var i = 1; i <= 5; i++) {
  ;(function(j) {
    setTimeout(function timer() {
      console.log(j)
    }, j * 1000)
  })(i)
}
// setTimeout第三个参数会被当成函数的参数,原理和闭包同理
for (var i = 1; i <= 5; i++) {
  setTimeout(
    function timer(j) {
      console.log(j)
    },
    i * 1000,
    i
  )
}
// 使用局部变量
for (let i = 1; i <= 5; i++) {
  setTimeout(function timer() {
    console.log(i)
  }, i * 1000)
}

有两点

  1. setTimeout 是个异步函数,所以会先把循环全部执行完毕,这时候 i 就是 6 了,所以会输出一堆 6
  2. 使用的var命名的都是是全局变量,每次循环都会覆盖原来的值,所以使用let就可以避免。闭包同理(变量通过参数传入,不会被覆盖)。