JavaScript中的闭包

一、作用域环境

  1. 在js作用域环境中访问变量是由内向外的,内部作用域可以获得当前作用域下的变量,和当前作用域外层作用域下的变量
  2. 外层作用域无法访问内部函数的变量
  3. 不同的函数作用域中不能相互访问彼此间的变量

二、什么是闭包

在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包。

如果我们想在一个函数内部也有限权访问另一个函数内部的变量,那么就可以使用闭包;

闭包的概念

  1. 函数嵌套
  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()  // 2  2
f()  // 3  2

这段代码的意思是,将fn的返回值(一个匿名函数)赋给f,f()表示调用fn()返回的匿名函数。

为什么第二次调用a和b的值不一样?

  1. 一般情况下,函数调用结束后,这个函数和函数内相关的变量都会被一起销毁,但是f()表示调用fn()里面返回的匿名函数,这意味着每次重新创建和销毁的都是这个匿名函数以及匿名函数中的变量
  2. fn()中只是a变量被引用了,每次调用结束这个变量a和fn函数并不会销毁,所以无论调用多少次b永远是2,但是a会持续增加
  3. 因为a不会被销毁,所以会造成内存消耗

利用闭包的特性保存变量

例:在for循环中每隔0.5秒打印出当前循环的次数

错误解法

        for(var i = 0; i < 5; ++i ){
                setTimeout(()=>{
                    console.log(i)
                },500)
        }

你会发现控制台直接输出了5个5
在这里插入图片描述
因为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毫秒输出一次

在这里插入图片描述

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>num)中的num是当前函数作用域下的num,也就是全局作用域下的num = 1 ,而不是fn1中的num = 10

所以 5>1,控制台输出5

四、闭包的优缺点

优点

  1. 形成私有的执行上下文,使内部私有变量不受外界干扰
  2. 避免命名冲突
  3. 解决循环绑定引发的索引问题

缺点

变量不会被垃圾回收机制回收、销毁,导致内存泄漏

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值