说说闭包

671 阅读4分钟

1. 什么是闭包

在一个函数内部创建另一个函数,即为闭包,闭包的特殊之处在于如果将这个闭包(内部的函数)返回,即使外部函数执行完毕,闭包仍能正常访问外部函数中的变量。

2. 闭包的原理

闭包仍能访问外部函数中的变量的原因是,即使外部函数执行完毕,此时该函数的作用域链被销毁,但是该函数的变量对象仍在闭包的作用域中。详细介绍参见闭包

3.闭包的使用

3.1 经典例子

    //未使用闭包
    var arr = [];
    for(var i=0;i<10;i++){
        arr[i] = function(){
            console.log(i)
        }
    }
    for(let a of arr){
        a(); // 皆为10
    }
    //使用闭包
    var arr = [];
    for(var i=0;i<10;i++){
        //这里使用了立即执行函数,在该函数内部,返回了一个函数,即为闭包,此时情况符合预期
        arr[i] = (function(i){
            return function(){
                console.log(i);
            }
        })(i);
    }
    for(let a of arr){
        a(); //0,1,2,3,4,5,6,7,8,9
    }

3.2 闭包中的this

在不使用bind,call,apply的情况下,闭包中的this即为全局对象(window)。先看代码

    var obj = {
    name:'wang',
    age:12,
    getName:function(){
        return function(){
            console.log(this===global); //浏览器下全局对象为window
        }
     }
    }

    obj.getName()();  //打印true

从以上代码中可以看出,闭包中的this即为全局对象,为什么不是外层函数的this?原因是每个函数在被调用时,都会自动获得两个特殊的变量:this和arguments。 闭包在搜索这两个变量时,只会在其活动对象中进行搜索,不会沿着作用域往上查找,因此永远不可能直接访问到外部函数中的this值。如果想使用外层函数中的this,可以将this保存到闭包可以访问的变量里,就可以让闭包访问到这个this。

    var obj = {
    name:'wang',
    age:12,
    getName:function(){
        var self = this;
        return function(){
            console.log(self.name);
        }
     }
    }

    obj.getName()();  //打印处'wang'

3.3 块级作用域

ES5下,javascript没有块级作用域,利用匿名函数可以模仿出块级作用域。

    (function(){
        var name='wang';
        var age=12;
    })()  //需将匿名函数用括号包裹起来。原因是javascript将function关键字作为函数声明的开始,而函数声明后面不能跟括号,而函数表达式则无此限制。
    console.log(name,age) //undefined,undefined

以上的代码使用匿名函数,并立即执行该函数,模拟了一个块级作用域,在匿名函数内声明的变量,外部函数无法使用。

以上是在全局作用域中模仿块级作用域,当在函数内部,临时需要一些变量,使用同样的方法,也可以构造块级作用域。

    function outer(num){
        (function(){
            for(var i=0;i<num;i++){
                console.log(i);
            }
        })();  //闭包
        console.log(i);  //undefined
    }

3.4 私有变量

javascript没有提供私有成员的语法,但是利用在函数内部定义的变量,包括函数的参数,函数内部的变量和函数,无法在函数外被访问的特性,使用闭包,也可以模拟出私有成员。

有权访问函数内变量的方法被成为特权方法。

特权方法的定义有两种:

  • 在构造函数中定义特权方法。
        function Person(){
            //私有变量和私有函数
            var name='wang';
            function getName(){
                return name;
            }
            //特权方法
            this.publicMethod = function(){
                name='zhou';
                return getName();
            }
        }
        var person = new Person();
        console.log(person.publicMethod()) //打印出'zhou'
        console.log(person.name) //undefined    
        
    
  • 静态私有变量

可以在私有作用域里创建私有变量或函数,以及特权方法。

    (function(){
        //私有变量和私有函数
        var name='wang';
        function getName(){
             return name;
        };
    
        //构造函数,注意两点:
        //1.使用的是函数表达式,而不是函数声明,因为在此处函数声明只能声明出局部函数。
        //2.Person未声明,初始化未经声明的变量,会创建一个全局变量。
        //以后这些操作是为了将Person创建为一个全局的函数。
        Person = function(){};
        
        
        //特权方法定义在Person函数的原型上,这将使私有变量/函数被所有的实例共享,原因是,特权方法定义在原型上,所以所有的实例都共享该方法,而该方法又是一个闭包,这个闭包的作用域中包含着name。
        Person.prototype.publicMethod = function(){
            name='zhou';
            return getName();
        }
    })();
  • 单例模式的增强 单例即是只有一个实例的对象,以对象字面量创建的对象即为单例对象。可以为单例对象添加私有变量和特权方法,增强单例对象。
    var person = function(){
        //私有变量/函数
        var name='wang';
        function getName(){
            return name;
            
        }
        return {
            publicPorterty:true,
            publicMethod:function(){
                name='zhou';
                return getName();
            }
        }
    }()  //立即执行。
    //该函数在定义了私有变量/函数后,返回一个对象,由于这个对象是在函数体中定义的,有权访问函数内定义的变量和方法。

以上严重参考高程,记录下这些文字,是为了方便记忆,如能帮到你,也是极好的。