javascript:闭包的总结

*媒介:此次总结闭包,离别参考了《js高等程序设计》、廖雪峰先生的网站、另有《js忍着秘笈》,好了,废话少说,黑喂狗~~~

---------------------庄重分割线-------------------*

1.js函数中的作用域链

没错,闭包照样要从作用域链提及,要明白闭包必需从函数第一次被挪用时发生了什么入手,先看一个例子,代码:

    function compare(value1,value2){
        if(value1 < value2){
            return -1;
        }else if(value1 > value2){
            return 1;
        }else{
            return 0;
        }
    }
    var result = compare(5,10);  //全局作用域中挪用

起首定义了一个compare函数,然后又在全局作用域中挪用了它。当挪用compare()时,会建立一个包括(this,arguments,value1,value2)的运动对象,而全局实行环境的变量对象(this,result,compare)在compare()实行环境的作用域链中处于第二位。在这例子中,当挪用compare()函数时,会为函数建立一个实行环境,其作用域链包括两个变量对象:当地运动对象和全局对象,在函数中接见一个变量时,会先从当地作用域中查找,找不到则向上查找外层函数的作用域中是不是有,直到全局作用域。当函数实行终了后,部分运动对象就会被烧毁,内存中仅保留全局作用域,然则闭包状况有所不同。

2.闭包中的作用域链

先看一个例子:

    function createComparisonFunction(propertyName){
        return function(object1,object2){
            var value1 = object1[propertyName];
            var value2 = object2[propertyName];
            
            if(value1 < value2){
                return -1;
            }else if(value1 > value2){
                return 1;
            }else{
                return 0;
            }
        };
    }

    var compareNames  = createComparisonFunction("name");
    var result = compareNames({name:"Jack"},{name:"Rose"});
    compareNames = null;   //烧毁匿名函数
    

上面的例子中,在createComparisonFunction函数内部定义了一个匿名函数,它的作用域链包括外部函数createComparisonFunction()的运动对象和全局变量对象,所以匿名函数中可以接见createComparisonFunction()函数中的propertyName。
最主要的是:createComparisonFunction()函数在实行终了后,它的实行环境作用域链会被烧毁,其运动对象依旧会留在内存中,这就是为何上面的代码中,实行:
var compare = createComparisonFunction("name")今后,createComparisonFunction()函数已实行终了,然则依旧可以鄙人一行代码中实行比较大小的操纵。
建立的比较函数被保留在变量compareNames中,设置它即是null,消除该函数援用,渣滓接纳。

3.闭包与变量

副作用:

闭包只能获得外层函数中任何变量的末了一个值。看下面例子:

    function createFunctions(){
        var arr = new Array();
        for(var i=0; i<10;i++){
            arr[i] = function(){
                return i;
            };
        }
        return arr;
    }
    
    var result = createFunctions();
    var f1 = result[0];
    var f2 = result[1];
    var f3 = result[2];
    //实行函数
    alert(f1());    //10
    alert(f2());    //10
    alert(f3());    //10

这个例子中好像每一个函数都应该返回本身的索引值,实际上每一个匿名函数返回的都是10。因为每一个匿名函数中都保留着createFunctions()函数的运动对象,所以它们援用的是同一个变量i,函数createFunctions()返回后,变量i的值都是10,此时每一个函数都援用着保留变量i的同一个变量对象,所以每一个函数内部i的值都是10.

注重:上述函数的挪用历程:实行createFunctions()函数,而且把函数实行效果赋值给变量result,如今result是一个数组,再把result[0]赋值给f1,f1实际上代表的是内部的匿名函数,如今实行这个函数:f1(),就会获得i的值。

革新

可以经由过程建立另一个匿名函数强迫让闭包的行动相符预期,看下面例子:

    function createFunctions(){
        var result = new Array();
        for(var i = 0; i < 10; i++){
            result[i] = (function(num){
                return function(){
                    return num;
                };
            })(i);
        }
        return result;
    }
    
    var final = createFunctions();
    var f1 = final[0];
    alert(f1());   //0

此次没有直接把闭包赋值给数组,而是定义了一个匿名函数,并马上实行该函数的效果赋值给数组。这个匿名函数有一个参数num,也就是终究要返回的值,挪用这个匿名函数时,传入了变量i,因为函数参数是按值通报的,所以会把变量i的当前值通报给num,这个匿名函数内部,又建立并返回了一个接见num的闭包,如许,最内里的匿名函数会锁住外层匿名函数通报进来的num值即当前i的值,而且返回num,如许num就会即是此时的i的值而且赋值给数组。

4.闭包就是匿名函数吗?

上述代码中很轻易让人误会:闭包就是一个匿名函数,其实不然,看下面例子:

    var outName = "表面的名字";
    var later;
    function outerFunction(){
        var innerName = "内里的名字";
        
        function innerFunction(){
            alert("I can see the "+outName);
            alert("I can see the "+innerName);
        }
        later = innerFunction;
    }
    outerFunction();
    later();

上述代码中,在outerFunction()函数中,将内部函数innerFunction()赋值给全局变量later,实行完倒数第二步:outerFunction();今后,实行later();依旧可以可以接见到内部变量innerName。
因为在外部函数outerFunction中声明innerFunction()函数时,不仅声清楚明了函数,还建立了一个闭包,该闭包不仅包括函数声明,还包括了函数声明的那一时候该作用域中的一切变量。

5.闭包的用途

私有变量

上述例子可以看出,闭包可以用来封装私有变量,就像java中的在对象内部封装一个private私有变量一样。
看下面例子:

    function createCounter(){
        var num = 0;
        this.getNum = function(){
            return num;
        };
        this.num = function(){
            num++;
        };
    }
    
    var counter = new createCounter();
    counter.num()  //挪用计数器一次,使num值加1
    //测试代码
    alert(counter.getNum());  //1
    alert(counter.num());    //undefined
    

上面例子顶用组织函数形式建立了一个计数器函数,然后对函数举行实例化,在组织函数内部,我们定义了一个变量num,它的可接见性只能在组织器内部,定义了一个getNum()要领,该要领只能对内部变量举行读取,但不能写入。然后又定义了要领num(),经由过程末了两行测试代码可以看出,可以经由过程存取要领getNum猎取私有变量,然则不能直接接见私有变量。

总结:私有变量的意义是,我们假如想对num举行加减乘除的操纵,只能在createCounter内部,外部只能接见内部举行逻辑操纵后的值,而不能接见带有对num值举行操纵的要领,如许,我们就可以把本身的营业逻辑封装在函数内部的闭包中,只需要暴露出接口让外部猎取想要获得的值就可以了,也就是说主动权完整在你定义函数时,外部只能看和猎取,而不能举行对变量值的转变的操纵。

上述的目标就是建立一个用于接见私有变量的公有要领。看下面代码:

    function Person(name){
        this.getName = function(){
            return name;
        };
        this.setName = function(){
            name = value;
        };
    }
    
    //测试
    var person = new Person("Jack");
    alert(person.getName());     //Jack
    person.setName("Rose");
    alert(person.getName());     //Rose

上面的代码在组织函数内部定义了两个要领:setName和getName,这两个要领都可以在组织函数外部接见和有用,而且都有权接见私有变量name,但在Person组织函数外部,没有任何方法接见name,因为这两个要领是在组织函数内部定义的,所以做为闭包可以经由过程作用域链接见name。上述在组织函数中定义特权要领有一个瑕玷,就是必需要运用组织函数形式来到达这个目标,如许针对每一个实例都邑建立一样一组新要领。

解决方法:静态私有变量
看下面代码:

    (function(){
        var name = "";
        Person = function(value){
            name = value;
        };
        Person.prototype.getName = function(){
            return name;
        };
        Person.prototype.setName = function(value){
            name = value;
        };
    })();
    
    //测试函数
    var person1 = new Person("Jack");
    alert(person1.getName());         //Jack
    person1.setName("Rose");
    alert(person1.getName());         //Rose
    
    var person2 = new Person("Will");
    alert(person1.getName());          //Will
    alert(person2.getName());          //Will
   

上述代码中,Person组织函数与getName()和setName()要领一样,都有权接见私有变量name,name变成了一个静态的、由一切实例同享的属性。在一个实例上挪用setName会影响一切的实例。或许新建一个Person实例都邑给予name属性一个新值。

上述两个要领:第二种建立静态私有变量会因为运用原型而增长代码复用,但每一个实例都没有本身的私有变量。

模拟块级作用域

上面举过例子,js没有块级作用域,可以经由过程匿名函数马上实行把块级作用域包裹起来,如许就有了块级作用域。看下面代码:

    function outputNumbers(count){
        (function(){
            for(var i = 0; i<count; i++){
                alert(i);
            }
        
        })();
        alert(i);   //此处会致使一个毛病,显现i是没有定义的。
        alert(count);
    }
    //测试函数
    outputNumbers(5);

上述函数中,在for轮回外部插入了一个私有作用域,在匿名函数中定义的任何变量,都邑在匿名函数实行结束时被烧毁。因而变量i只能在for轮回中运用,运用后即被烧毁,因而上面的代码实行会如许:
1.实行测试函数后,会弹出5个弹窗,会显现0,1,2,3,4
2.实行完匿名函数后,i即被烧毁,所以实行alert(i);会报错。
3.可以接见变量count,因为这个匿名函数时一个闭包,它可以接见包括作用域中的一切变量。

这类手艺经常在全局作用域中被用在函数外部,从而限定向全局作用域中增加过量的变量和函数,看下面代码:

    (function(){
        var now = new Date();
        if(now.getMonth() == 0 && now.getDate() == 1){
            alert("除夕快活");
        }
    })();

上述代码放在全局作用域中,可以用来肯定哪一天时1月1日除夕,now是匿名函数中的部分变量,不用在全局作用域中建立它。

先写这么多吧,今后再增加~~~~~

    原文作者:yangdepp
    原文地址: https://segmentfault.com/a/1190000005753650
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞