JS闭包

146 阅读5分钟

一、什么是闭包

学习闭包我们要清楚函数作用域、内存回收机制、作用域继承。

1.1 函数作用域 作用域我们可以认为它是一个封闭的盒子,只让它在这个盒子里面进行操作,也可以称这个盒子为独立作用域。在js中,一个函数要执行时就会在内存里面创建一个独立作用域————封闭的盒子。

比如在函数中第一一个变量,只能在函数这个独立作用域中使用(也就是封闭的盒子)。只要跳出这个作用域,就找不到该变量了。

而且函数执行完毕之后,这个独立作用域或(封闭的盒子)就会删除。有一种情况下这个封闭的盒子是不会删除的,那就是“闭包”,后面会讲到。

1.2 内存回收机制 内存回收机制就是不在用到的内存空间,系统会自动进行清理出空间提供给其他程序使用。那回收的前提是什么呢?

内部函数引用外部的函数的变量,外部函数执行完毕,作用域也不会删除。从而形成了一种不删除的独立作用域。

某一个变量或者对象被引用,因此在回收的时候不会释放它,因为被引用代表着被使用,回收机制不会对正在引用的变量或对象进行回收的。

1.3 作用继承 所谓的作用域继承,就像是儿子可以继承父亲的财产一样。比如我这里有一个大的盒子作为一个父级的作用域,然后在这个大的盒子里边放一个小的盒子,作为子作用域。我们规定可以在小盒子中获取到大盒子中的东西,大盒子不能获取小盒子里的东西就称为作用域继承。

在 JS 中,道理是一样的,在一个函数里边我们再声明一个函数,内部函数可以访问外部函数作用域的变量,而外部的函数不能获取到内部函数的作用域变量。

那好,上边的这几个概念理解了之后,什么是闭包对你来说已经不是什么问题。

什么是闭包,那就是在一个函数里边再定义一个函数。这个内部函数一直保持有对外部函数中作用域的访问(小盒子可以一直访问大盒子但大盒子不能访问小盒子)。

函数执行,形成一个独立作用域,保护里边的私有变量不受外界的干扰,除了保护私有变量外,还可以存储一些内容,这样的模式叫做闭包

闭包的作用

  • 1.保护变量私有化
    定义在函数内部的变量就叫做私有变量

  • 2.在函数外部访问函数内部的私有变量
    利用闭包函数访问

闭包的特点

  • 1.保护变量了私有化
    优点:不去污染全局变量
    缺点:外部不能去访问,需要闭包函数
  • 2.可以在函数外部访问函数内部的变量
    优点:不局限与私有变量
    缺点:外部访问需要闭包函数
  • 3.变量的生命周期
    优点:变量的生命周期被延长了
    缺点:一个不会被销毁的函数空间

什么时候使用闭包呢?

  • 希望重用一个对象,又保护对象不被污染篡改时

缺点: 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。

二、解决问题:

1.闭包解决循环添加单机事件总是打印最后一个索引的问题

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

image.png 此处点击的是数字为2的按钮,打印为1,i从0开始计算

二、解决定时器不能传参的问题

    function f(a){
            setTimeout(function(a){
                alert(a) //就算是传参也是ndefined
            })
        }
        f();

image.png

解决这个问题:

    function f(x){
            return function f1(){
                alert(x)
            }
        }

        setTimeout(f(100),3000) //闭包向定时器参数

3.闭包持续增加一个值的大小(可以写在外面定义成全局变量,可是万一别人改了怎么办)

    function ff() {
            var num = 1;
            return function () {
                num++;
                alert(num)
            }
        }

        var ff1 = ff()
            ff1() // 2
            ff1() //3
            ff1() //4
            ff1() //5
            console.log(num); //报错,函数内能访问函数外的变量,反之不行 

三、两个小案例:

3.2、函数作为返回值:

    function create() {
        const a = 100;
        return function () {
            console.log(a);
        }
     }

     const fn = create();
     const a = 200;
     fn(); 

执行过程:

函数执行在全局作用域,没有函数包裹, a = 200 也是全局作用域 函数定义是在 create 这个作用域 a = 100 也是在create这个作用域 在函数里面打印a,a是一个自由变量,自由变量寻找的时候 它会在它定义的地方去寻找,往上一级作用域寻找,这个 上一级作用域应该是他函数定义的作用域的上一级

3.2、函数作为参数:

    // 函数作为参数
    function print(fn) {
        let a = 200;
        fn();
    }

    let a = 100;
    function fn() {
        console.log(a);//100
    }
    print(fn);

同上层理念,我们执行fn函数调用的时候,执行的时候是在print这个作用域下面, 执行的是console.log(a) , a呢是一个自由变量,它在fn这个函数作用域里面, 它在fn这个作用域里面寻找有没有定义a,没有就向上一级寻找,找到 a = 100

得出结论:

所有自由变量的查找,是在函数定义的地方,向上一级作用域查找,不是在函数执行的地方

四、总结:

闭包:形成闭包的条件

    1. 一个不会被销毁的函数执行空间
    1. 函数内部直接或者间接的返回一个函数
    1. 内部函数操作(访问, 赋值) 着外部函数的变量
  •   当三个条件都满足的时候,我们管内部的函数叫做外部函数的闭包函数