谈谈js闭包

闭包问题一直是面试中提到频率最高的问题,那到底什么是闭包呢,百度一下,我们会看到各种层出不穷的答案

所以,闭包是什么呢?

闭包的定义

  • 比较权威的答案,在函数内部定义的变量或方法在函数外部被引用时,就形成了闭包

    但这种说法还是比较片面的,比如我们在函数内部定义方法,并在函数内部执行,也会形成闭包

    image

  • 红宝书中的定义,闭包是指有权访问另一个函数作用域中的变量的函数

    image

    之所以函数能够访问另一个函数作用域中的变量,即使这个内部函数(匿名函数)被返回了,而且是在其他地方被调用了,但它仍然可以访问外部函数中的变量 closure,是因为内部函数的作用域链中包含外部函数的作用域

    当某个函数被调用时,会创建一个执行环境及相应的作用域链

    《JavaScript权威指南》有提到,函数的执行依赖于变量作用域,这个作用域是在函数定义时决定的,而不是函数调用时决定的

    每个函数都有一个活动对象,在作用域链中,外部函数的活动对象始终处于第二位,外部函数的外部函数的活动对象处于第三位,......直至作为作用域链终点的全局执行环境

    在函数执行过程中,为读取和写入变量的值,就需要在作用域链中查找变量

    但我觉得红宝书中的说法也不完全正确,我们可以看看开发者工具中的执行结果

    image

    所以说,闭包指向的是定义内部函数访问的变量的外部作用域,也可以说,闭包是代码块(函数)和创建该代码块的上下文中数据的结合

  • 官方定义,闭包是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分

    我的理解是函数保留了对外部函数变量的引用,简单说就是引用了外部变量,这个变量没有被GC回收,仍存在于函数的作用域链中,这样的函数,叫做闭包

创建闭包的方式

创建闭包的常见方式,就是在一个函数内部创建另一个函数

方式有很多种,比如,可以是在函数内部 return 一个函数(如上),也可以是在函数定义一个内部变量函数,并在函数内部执行(如上),或者是定义一个全局变量函数,在全局执行

image

或者是在定义一个对象方法,在外部执行

image

闭包的作用

闭包很好地解决了变量过多的问题

我们先来看一个例子

<button>1</button>
<button>2</button>
<button>3</button>
var buttons = document.querySelectorAll('button')
for (var i = 0; i < buttons.length; i++) {
  !function(index){
    buttons[i].onclick = function() {
      alert(index + 1)
    }
  }(i)
}

通过立即执行函数形成的闭包很好的解决了button变量的问题,如果没有闭包,我们就需要写多个这样的button点击事件

而且,如果没有闭包,我们以这种方式来定义点击事件,会出现常见的问题

var buttons = document.querySelectorAll('button')
for (var i = 0; i < buttons.length; i++) {
  buttons[i].onclick = function() {
    console.log(i)
  }
}

这样,每个函数打印的i值其实都是3。因为每个函数的作用域链中都保存着全局的活动对象,所以它们引用的都是同一个变量i。当开始执行 onclick 事件时,变量i的值已经是3了,此时每个对象都引用着保存变量i的同一个变量对象,所以在每个函数内部i的值都是3