#yyds干货盘点# 详解JavaScript中的闭包

少年乘勇气,百战过乌孙。这篇文章主要讲述#yyds干货盘点# 详解JavaScript中的闭包相关的知识,希望能为你提供帮助。
一、作用域环境

  1. 在js作用域环境中访问变量是由内向外的,内部作用域可以获得当前作用域下的变量,和当前作用域外层作用域下的变量
  2. 外层作用域无法访问内部函数的变量
  3. 不同的函数作用域中不能相互访问彼此间的变量
二、闭包的作用
如果我们想在一个函数内部也有限权访问另一个函数内部的变量,那么就可以使用闭包,闭包的本质就是在一个函数内部去创建另一个函数。
三、闭包的体现
1、将函数作为返回值
function fn() let a = 1 return function () let b = 1 console.log(a:,++a) console.log(b:,++b)let f = fn() f()// 22 f()// 32

这段代码的意思是,将fn的返回值(一个匿名函数)赋给f,f()表示调用fn()返回的匿名函数。
为什么第二次调用a和b的值不一样?
利用闭包的特性保存变量
例:在for循环中每隔0.5秒打印出当前循环的次数
错误解法
for(var i = 0; i < 5; ++i ) setTimeout(()=> console.log(i) ,500)

你会发现控制台直接输出了5个5
#yyds干货盘点# 详解JavaScript中的闭包

文章图片

因为js代码执行时有一个任务队列,for循环属于微任务,setTimeout属于宏任务,宏任务会等到微任务执行完毕才会进入javascript的调用栈。具体细节推荐阅读这篇文章:一篇文章快速搞懂JavaScript事件循环、任务队列、同步异步和阻塞非阻塞
也就是说等setTimeout执行时for循环已经执行完毕了,此时i=5,所以输出了5个5
正确解法
for(var i = 0; i < 5; ++i ) (function(i) setTimeout(()=> console.log(i) ,i*500) (i))// for循环里面是一个立即执行函数,最后面的‘(i)’表示往这个立即执行函数里面传递的参数

  1. 将setTimeout放进立即执行函数里面,利用闭包存储每次循环时变量i的值
  2. 然后再将每个定时器设置不同的时间,这样就相当于每一次循环开一个定时器,每个定时器之间相差500毫秒,实现每隔500毫秒输出一次
#yyds干货盘点# 详解JavaScript中的闭包

文章图片

es6中的写法
使用let定义变量i
for(let i = 0; i < 5; ++i ) setTimeout(()=> console.log(i) ,500)

var定义的i在全局范围内都有效,所以全局只有一个变量i。每一次循环,变量i的值都会发生改变,但是里面的i,指向的都是同一个i,导致运行时输出的是最后一轮的i的值,也就是 5
es6中引入了let,变量i是let声明的,具有块级作用域,当前的i只在本轮循环有效,所以每一次循环的i其实都是一个新的变量,javaScript 引擎内部会记住上一轮循环的值,在上一轮循环的基础上进行计算然后初始化本轮的变量i
不过,虽然能顺序输出12345,他们也并不是间隔500毫秒,而是一起输出的。因为let虽然能记住每一次的变量,但是setTimeou的执行顺序不会改变
2、将函数作为参数
var num = 1 var fn = function(a) if(a > num) console.log(a) else( console.log(num) )function fn1(fn2) var num = 10 fn2(5)fn1(fn)

这段代码会输出5,因为fn被作为参数传入到fn1中,当执行fn2(5)时(fn2(5)相当于fn(5)),5作为参数传入fn中,这时if(a& gt; num)中的num是当前函数作用域下的num,也就是全局作用域下的num = 1 ,而不是fn1中的num = 10
所以5> 1,控制台输出5
四、闭包的优缺点
优点
  1. 形成私有的执行上下文,使内部私有变量不受外界干扰
  2. 避免命名冲突
  3. 解决循环绑定引发的索引问题
缺点
【#yyds干货盘点# 详解JavaScript中的闭包】变量不会被垃圾回收机制回收、销毁,导致内存泄漏

    推荐阅读