重学前端之 关于闭包

刚开始学习前端的时候,学习闭包晕头转向,都不知道什么是什么,在接触变成一段时间后发现因为自己基本功不扎实的原因导致基本概念不理解所以对闭包根本无法掌握,这篇文章以我自己的理解记录一下学习对于闭包的学习历程。

一.局部变量和全局变量

局部变量:可以简单理解成函数内部申明的变量
全局变量:可以简单理解成最外层被申明的变量
复制代码
var a = 'web'
function Foo(){
    console.log(a)
}
console.log(a)
Foo()
复制代码

变量a和函数Foo定义在最外层,所以在代码的任何地方都可以访问到他们。

function Foo(){
    var a = 'web'
    console.log(a)
}

console.log(a)  //报错
Foo()       // web
复制代码

变量a在函数Foo定义的内层(局部变量),所以只能在函数内部访问,外部无法访问。
所以全局变量在任何位置都可以访问,但是局部变量只能在当前作用域下面访问 *** 未声明的变量,自动定义为全局变量(无论在函数内部还是外部) ***

二.函数作用域

说道函数作用域,就要提及一个概念是函数的作用域链

作用域链

当一个函数创建的时候,我们需要使用其中的变量,函数内部没有创建这个变量,我们会去上一级去寻找这个变量看是否被创建拿到他使用他,如果上一级没有,继续去上一级去寻找,这一个寻找路径(内部,上一级,上一级的上一级...)就被称作函数作用域链

作用域
作用域其实是指一个包含了所有在同一个区域声明的变量和函数的集合,那么如何决定这些变量数据和函数是属于同一区域的呢?这就由他们最初声明时的位置来决定的。我们在看一段代码来理解一下作用域以及作用域链
复制代码
var a = 1
function fn1(){
  function fn2(){
    console.log(a)
  }
  function fn3(){
    var a = 4
    fn2()
  }
  var a = 2
  return fn3
}
var fn = fn1()
fn() //2
复制代码

他为什么会输出2呢,首先我们看一下这个函数体,fn是其实就是fn1,而fn1()是内部的fn3,fn3内部有申明了一个变量a = 4,并且执行函数fn2(),fn2打印a,但是在fn2当前内部作用域没有变量a,这个时候他会去定义他的环境里面找变量a,也就是fn1()内部,当然fn1内部也申明了变量a = 2,所以他会打印2 刚开始很容易弄错他会打印4,函数的内部需要变量的话,他寻找的点是内部-》词法作用域(也就是定义他的外部作用域)=》外层作用域这个顺序依次去寻找

三.闭包

外部不能使用函数的内部变量,如果使用呢,这个时候我们就可以使用闭包这个手段,闭包首先我们理解成一个手段,什么手段,可以从外部拿到函数内部变量的手段,首先我们看一个简单的闭包
复制代码
    function add() {
      var a = 1
      function addNum(){
        a++
        return a 
      }
      return addNum
    }
    var addFunc = add()
    
    console.log(addFunc())      //2
    console.log(addFunc())      //3
    console.log(addFunc())      //4
    console.log(addFunc())      //5
    console.log(addFunc())      //6
    console.log(addFunc())      //7
    console.log(addFunc())      //8
复制代码

这个时候肯定会有疑问了,怎么我就能拿到内部变量,并且这个变量可以一直保存
首先我们看一下这个函数,这个函数add内部定义了一个变量和一个函数addNum(),并且这个addNum函数对这个局部变量进行了操作++,函数最后把这个值给return了出去,并且add函数又把addNum这个函数当做一个值return了出去,那么add()这个函数就是他内部的addNum()
这个时候赋值给addFunc这个变量,这个变量就获取到add()(也就是addNum()也就是a的值)
然后console.log(addFunc())就可以获取到这个内部值并进行操作
上面说的只是表现,也就是我们看到的东西,接下来我们来说一说我们看不见的
1.当函数被申明,执行过后,他内部的变量被释放,也就是说我申明普通函数使用后,再去调用一次的时候,他内部的变量还会变得和原谅一样,这并不是函数内部的特性,而是为了让内部的变量不污染全局,在内部申明之后再次使用后他会从新被使用,但是如果函数内部的变量使用的是全局变量就可以不用考虑这个问题
2.因为变量申明在调用的时候会被重新执行,那么我们就要用个小手法来让这个变量保存住,并且能被外部使用
2-1.被外部使用很简单,return出去就可以了
2-2.保存住我们可以在函数内部创建一个函数,并且把获取值进行操作以后return 给此函数,在把这个函数return 出去给外部函数,相当于在函数内部创建一个作用域,保存住了内部的变量
我们扩展一下,加深一下对于闭包的理解,需求是我们需要一个汽车,我们可以设置他的速度,但是外部的变量不能随意获取到他内部的变量,并且在外部还能操作这个变量,使用闭包我们可以写成

 function catFunc(){
    var speed = 0;
    function set(s){
        speed = s
    }                 
    function get(){
         return speed
    }
    function speedUp(){
         speed++
    }
    function speedDown(){
         speed--
    }
     return {
        set,
        get,
        speedUp,
        speedDown
    }
    }
   
var Car = catFunc()
Car.set(30)                         
console.log(Car.get())      //30
Car.speedUp()
console.log(Car.get())      //31
Car.speedDown()
console.log(Car.get())      //30
    
复制代码

这个函数我们可以理解成 在Car内部创建了四个函数都在修改这个变量,但是最后把函数名组成一个对象return出去,这样内部的函数保存住了变量,return出去的函数也就是方法可以操作内部变量 看了实际操作以后,我们看一下关于闭包常见的面试题 闭包常见面试题

//如下代码,输出的是什么
for(var i=0; i<5; i++){
  setTimeout(function(){
    console.log('delayer:' + i )
  }, 0)
}
复制代码

如果大家对任务队列或者异步有些了解,就不能看出,i是全局变量,在for循环结束以后,才会执行setTimeout里面的内容,这个时候打印i的时候,就会去寻找全局的的变量i,也就是刚刚循环结束以后的那个i,这个时候打印的当然就是五个4
如果我们想解决这个问题,使它打印0-5怎么做到呢,我们是否是需要创建一个作用域保存住每次循环次数打印出来,当然我们可以使用闭包这个手法来保存这个变量

for(var i = 0 ;i < 5 ;i++){
  (function(j){
      setTimeout(function(){
        console.log('delayer'+j)
      },0)
  })(i)
}
复制代码

以上这段代码,我们使用立即执行函数来创建一个作用域,使用i作为形参传入使用立即执行函数来保存住这个变量,这个时候我们打印出来的内容当然就是0-4了 上述代码还有一种写法,思想也是一样,创建作用域来保存我们的变量

for(var i = 0 ;i < 5 ;i++){
  setTimeout((function(j){
    return function(){
      console.log(j)
    }
  })(i),0)
}
复制代码

思想是一样的,但是我们的做法不一样,我们在异步的setTimeout中使用立即执行函数保存住变量i然后return出去,这样setTimeout内部的函数其实就是return出来的console.log(被保存的变量)

总结

其实闭包我们可以理解成保存变量的一个手法(创建作用域),因为项目中一个文件中的全局变量过多会造成变量的复燃,变量会很不规范已经命名很有可能造成冲突,所有我们可以使用闭包来保存住某个需求需要的变量,这边全局变量减少后,我们的代码也会特别清晰,代码对于开发者来说也是特别友好的,在下一个文章中,我会讲到函数节流和函数防抖,会使用闭包来封装函数达到函数复用的效果,小伙伴们喜欢的就来个素质三连(点赞,收藏,关注)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值