谈谈什么是闭包?

713 阅读3分钟

前言

说到闭包真的是让我头大的东西,一直以来都只是一个概念,平常也几乎不用,所以一直都没太搞懂。都是最近在看防抖,节流函数的写法,推荐使用闭包来写,所以才重新来理解闭包,防抖和节流之后也会单独写一篇文章来阐述吧。

什么是闭包

MDN中对闭包的定义如下:

函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起构成闭包(closure)

上述句子换句话说就是:闭包是由函数及其引用的变量构成的。那么就有一个问题了,那岂不是所有函数只要内部引用外部的变量都能构成一个闭包,例如:

	let a = 1;
    function printA(){
      console.log(a);
    }

按照定义确实上述例子也构成闭包,但怎么跟我们平时见到的闭包不一样呢?

《JavaScript权威指南中》确实也写道:

从技术角度讲,所有的JavaScript都是闭包。

但从实际应用考虑,这样的闭包就没有存在的意义,所以MDN补充道:

也就是说,闭包可以让你从内部函数访问外部函数作用域。

所以引用的变量应该为函数作用域内的变量,换句话说,闭包应存在于函数作用域内部,例如:

    //a与printA构成一个闭包
	function func(){
      let a = 1;
      function printA(){
        console.log(a);
      }
      printA();
    }
    func();

看到这个例子有人可能会想,怎么我看到的闭包都会return一个函数呀?实际上是一样的,return出去只是为了外部调用而已,本质上一样的嘛。

关于为什么能够在内部函数中引用外部函数的变量,对作用域稍有了解的人就会知道:函数中引用变量时,会先从自身函数中读取,如果内部没有就逐层往上读取,直至读取到。这里特别提一点,函数依赖于变量作用域,这个作用域是在函数定义是决定的,而不是函数调用时决定的。(来源《JavaScript权威指南》) 所以寻找变量时应该从定义时的函数中往上寻找,而非调用时,举个例子:

	function func(){
      let a = 1;
      console.log(a);
    }
    let a = 2;
    func();  //1

闭包的简单应用

前面说了那么多,那么闭包究竟有什么用?闭包的常见用法就是提供间接访问一个变量的方法,有时候我们需要在某些地方用到一个变量,但是又不能定义为全局变量,存在着篡改的风险,那么我们就可以用定义位私有变量,然后通过闭包暴露出去,例如通过闭包写一个计数器,代码如下:

    function count(){
    	let num = 0;
            return {
                add: function () {
                    num++;
                    return num;
                },
                sub: function () {
                    num--;
                    return num;
                },
                reset: function () {
                    num = 0;
                    return num;
                }
            }
        }
        let counter = count();
        counter.add();  //1
        counter.add();  //2
        counter.sub();  //1
        counter.reset(); //0
        counter.add();  //1