函数闭包

186 阅读4分钟

作用域

在理解什么是闭包之前,我们要先理解作用域的概念,那么什么是作用域呢?

当前的执行上下文。值和表达式在其中 "可见" 或可被访问到的上下文。如果一个变量 或者其他表达式不 "在当前的作用域中",那么它就是不可用的。 作用域也可以根据代码层次分层,以便子作用域可以访问父作用域,通常是指沿着链式的作用域链查找,而不能从父作用域引用子作用域中的变量和引用。

这是一段MDN对作用域的说明,看样子似乎和原型链有些类似,有作用域链,查找变量也会沿着作用域链往上查找。还记得在之前的let,const和var的区别中,var会挂载在window上,但是let和const不在window,但是声明的变量可以在全局访问和使用,那let和const定义的变量又是存在哪里呢?我们看下面一段代码 image.png es6中规定var和function声明的全局变量,依旧是顶层对象(window)的属性,let,const,class声明的全局变量,不作为window的属性,放在全局的块级作用域scope中。那么什么情况会形成作用域呢?function声明的函数,内部的函数作用域和{}包起来的块级作用域是比较常见的作用域。

词法作用域

所谓的词法作用域就是在你写代码时将变量和块作用域写在哪里来决定,也就是词法作用域是静态的作用域,在你书写代码时就确定了

function createCounter () {
  let counter = 0
  const myFunction = function () {
    let x = 1

  }
  return myFunction

}

在上面这段代码中存在着三个作用域,createCounter存在作用域是全局,counter和myFunction存在createCounter的函数作用域下,变量x又存在于myFunction函数作用域下。我们要注意,函数在创建时会保存创建时的执行上下文的文本环境,可以理解为保存着上一级作用域中的变量

闭包

function createCounter () {
  let counter = 0
  const myFunction = function () {
    counter = counter + 1
    return counter

  }
  return myFunction

}
const increment = createCounter()这一步,increment这个变量实际是
const c1 = increment()
const c2 = increment()
const c3 = increment()
console.log(c1, c2, c3)  // 1 2 3

我们分析一下这段代码,先在全局作用域下声明了createCounter函数,函数内部定义了counter变量和myFunction,myFunction内部进行了counter的计算。那为什么全局作用域下没有counter这个属性,但是最后输出的结果却是对他进行了累加呢?

因为函数在创建时会保存创建时的执行上下文的文本环境,可以理解为保存着上一级作用域中的变量。也就是说在const increment = createCounter()这一步,increment这个变量实际是createCounter函数的执行结果即myFunction函数,myFunction函数内已经保存了创建的执行上下文中的那个变量counter

image.png

increment函数作用域内保存着两个作用域,global就是全局作用域,Closure实际就是我们说的闭包,即对上一级作用域内变量counter的引用。我们往下执行声明c1,counter+1,可以看到increment的Closure内的counter变成了1

image.png

继续往下执行语句,声明c2,c3同理,只要没有销毁increment,他的内部作用域会一直保存着counter的执行结果,也就是我们说的闭包。 所有的函数都可以理解为有闭包,甚至是在全局声明的函数,只是他的作用域是全局,可以访问到全局作用域内的所有属性,所以我们没有理解上的偏差。

我将永远记住闭包的方法是通过背包的类比。当一个函数被创建并传递或从另一个函数返回时,它会携带一个背包。背包中是函数声明时作用域内的所有变量。

使用场景

  • 函数防抖,共享timmer,每次输入的时候重新计时
      function debounce(func, wait) {
        // timer 写在闭包中,因此防抖也是闭包的一个应用
        let timer = null;

        return function () {
          if (timer) {
            clearTimeout(timer);
          }
          timer = setTimeout(() => {
            func.apply(this, arguments);
            timer = null;
          }, wait);
        };
      }
  • for循环拿到正确值,
for(var i=0;i<10;i++){
((j)=>{
  setTimeout(function(){
        console.log(j)//1-10
    },1000)})(i)
  
}  // 创建10个立即执行函数,利用闭包的特性保存当前下标

闭包的优缺点

优点

  • 避免全局变量污染
  • 保存私有变量 缺点
  • 可能会导致内存泄漏
  • 因为使用闭包会保存变量,会增加内存的消耗

面试题

(function () {
  var x = y = 1
})()
var z

console.log(x, y, z); // ReferenceError: x is not defined,1,undefined

这里的自执行函数形成了一个闭包,x相当于私有变量存在了闭包当中,所以在全局下找不到x变量会报错;y因为变量提升提升到了全局所以输出1;z在全局下定义但是没有赋值所以输出undefined

结语

写的不好,有错误的地方还请各位大佬多多指教,感恩家人🙏