JavaScript函数之闭包

作者: 微语博客 | 来源:发表于2021-07-31 00:14 被阅读0次

    什么是闭包

    闭包是JavaScript的难点,闭包产生的原因也是因为函数作用域的特性,函数作用域的内容可以回顾上一篇文章JavaScript函数之作用域。闭包是什么呢,很多文档资料对闭包的讲解也是非常的抽象,比如引用Mozilla对闭包的解释:一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)。简单的理解是:闭包其实是一个被引用的函数作用域。这个作用域是函数的一个私有空间,由于私有空间的变量被引用,从而形成闭包不让它销毁。

    回顾作用域

    在JavaScript中,作用域无非两种:局部和全局作用域。作用域的特点也很明显,内层可以访问外层的作用域。比如下面的示例:

    function myFun() {
        var num = 10;
        function inFun(){
            console.log(num);
        }
        inFun();//10
    }
    myFun();
    

    当执行外层函数myFun时,该函数产生了一个内部变量num和一个内部函数inFun,所以在myFun函数外部无法访问到内部的作用域,原因是作用域创建的顺序有先后。同理,嵌套函数也是一样。

    闭包

    上面的示例中,myFun的嵌套函数inFun执行后成功输出了num的值,这是我们意料之中的事,因为嵌套函数可以访问外部函数的作用域,但是如果我们要在myFun外面执行inFun函数呢,看下面的例子

    function myFun() {
        var num = 10;
        function inFun(){
            console.log(num);
        }
        return inFun;
    }
    var pubFun = myFun();
    pubFun();//10
    

    该示例中我们没有直接调用inFun函数,而是将它作为返回值返回了。在外层定义了一个全局变量pubFun,其值为myFun函数的调用,注意这里在声明变量pubFun时已经调用了myFun函数,所以再执行pubFun的时候输出了num的值。

    在本示例中,由于myFun执行完毕,num变量可能会被回收销毁,但是由于内部函数对其有引用,所以myFun函数形成了一个闭包,阻止局部变量被销毁,所以闭包可以理解为是一个作用域,包括该作用域的所有引用。

    计数器

    再看一个闭包的示例:

    function add() {
        var num = 0;
        function addx(x){
            return num+=x;
        }
        return addx;
    }
    var myAdd = add();
    console.log(myAdd(1));//1
    console.log(myAdd(1));//2
    console.log(myAdd(1));//3
    
    

    由于闭包,num变量不会被销毁或重置,所以每次执行都会累加,这样就形成了计数器。我们也可以让它的计数梯度为其它的值,比如下面:

    function add() {
        var num = 0;
        function addx(x){
            return num+=x;
        }
        return addx;
    }
    var myAdd = add();
    console.log(myAdd(2));//2
    console.log(myAdd(2));//4
    console.log(myAdd(2));//6
    

    当然,这样的应用就有很多了,其核心就是JS函数形成的闭包。

    闭包模拟私有方法

    学过Java的同学都知道,Java可以用private关键字来修饰私有方法,而原生的js并没有提供支持,我们可以使用闭包来模拟私有方法,私有方法只能被同一个类所调用。

    function math(x) {
        var num = 0;
        return {
            add : function (y) {
                return x + y;
            },
            multi : function (y) {
                return x * y;
            }
        };
    }
    var myMath = math(5);
    console.log(myMath.add(5));//10
    console.log(myMath.multi(5));//25
    
    

    例子简单,够用就好,不过这个示例还就可以使用立即执行函数优化,下一篇文章讲解立即执行函数

    闭包引发的性能问题

    因为闭包中的作用域引用无法释放,大量的使用闭包会严重的消耗内存,如果不是特定任务需要使用闭包,在其它函数中创建函数不是一个明智选择。比如下面的例子:

    function MyObj(name){
        this.name = name.toString();
        this.getName = function(){
            return this.name;
        }
    }
    

    类似这种示例,每次构造器被调用时,方法都会被重新赋值一次。应该关联于对象的原型,而不是定义到对象的构造器中。

    function MyFun(name){
        this.name = name.toString();
    }
    MyFun.protoype = {
        getName : function(){
            return this.name;
        }
    }
    

    但是也不建议重新定义原型,而是推荐在原型对象上添加原型方法。

    function MyFun(name){
        this.name = name.toString();
    }
    MyFun.protoype.getName = function(){
        return this.name;
    }
    

    这里就涉及到构造函数和原型的知识点了,先混个眼熟吧。

    相关文章

      网友评论

        本文标题:JavaScript函数之闭包

        本文链接:https://www.haomeiwen.com/subject/nqkhvltx.html