JavaScript闭包?看完这几个例子你就完全搞懂了!

理解闭包,首先要知道作用域链是个什么东西。

1.什么是作用域链

在 JavaScript 的最顶层代码中(也就是不包含在任何函数定义内的代码),作用域链有一个全局对象组成。在不包含嵌套的函数体内,作用域链上有两个对象,第一个是定义函数参数和局部变量的对象,第二个是全局对象。在一个嵌套的函数体内,作用域链上至少有三个对象。

当定义一个函数时,它实际上保存一个作用域链。

当调用这个函数时,它创建一个新的对象来储存它的局部变量,并将这个对象添加至保存的那个作用域链上,同时创建一个新的更长的表示函数调用作用域的“链”。

对于嵌套函数来说,每次调用外部函数时,内部函数又会重新定义一次,因为每次调用外部函数的时候,作用域链都是不同的。

内部函数在每次定义的时候都有微妙的差别,即每次调用外部函数的时候,内部函数的代码都是相同的,而关联这段代码的作用域链却是不相同的

2.什么是闭包

函数对象可以通过作用域链相互关联起来,函数体内部的变量都可以保存在函数作用域内,这种特性在计算机科学文献中称为“闭包”——是指函数变量可以被隐藏于作用域之内,因此看起来是函数将变量“包裹”起来了

例1

var scope = 'global scope';
function checkscope() {
    var scope = "local scope";
    function f() {
        console.log(scope);
    }
    return f();
}
checkscope();

执行函数之后打印

checkscope()函数声明了一个局部变量,并定义一个函数 f(), 函数 f() 返回了这个变量的值,最后将函数 f() 的执行结果返回。这个结果是显而易见的返回函数内局部变量,然后我们改动一下函数再看看。。。

var scope = 'global scope';
function checkscope() {
    var scope = "local scope";
    function f() {
         console.log(scope);
    }
    return f;
}
checkscope()();

上面例子也可以改为这样

var scope = 'global scope';
function checkscope() {
    var scope = "local scope";
    function f() {
         console.log(scope);
    }
    return f;
}
var f = checkscope();
f();

结果并没有改变,这是什么原因呢?

会看一下作用域的基本规则:javascript 函数执行用到了作用域链,这个作用域链是函数定义的时候创建的。嵌套的函数 f() 定义在这个作用域里,其中的变量scope一定是局部变量,不管在何时何地执行函数f(),这种绑定在执行f()时依然有效。简言之,闭包的这个特性就是:它们可以捕捉到局部变量(和参数),并一直保存下来,看起来像这些变量绑定到了在其中定义的外部函数。

3.利用闭包

例2

     function counter() {
            var n = 0;
            return {
                count: function() {
                    return n++;
                },
                reset: function() {
                    n = 0;
                }
            };
        }
        var c = counter(), d = counter();
        console.log(c.count());
        console.log(d.count());
        c.reset();
        console.log(c.count());
        console.log(d.count());

每次调用couter()都会创建一个新的作用域和一个新的私有变量。因此,变量c重置了n=0,所以第二次c,d执行count()的结果不一样。

例3

      function counter(n) {
            return {
                get count() {return n++;},
                set count(m) {
                    if(m>=n) {
                        n = m;
                    }else {
                        throw Error("count can only be set to a larger value")
                    }
                }
            }
        }
        var c = counter(1000);
        console.log(c.count);
        console.log(c.count);
        console.log(c.count = 2000);
        console.log(c.count);
        console.log(c.count = 2000);

这个版本的counter()函数并未申明局部变量,而只是使用参数n来保存私有状态,属性存储器方法可以访问n,这样的话,调用counter()的函数就可以指定私有变量的初始值了。

例4(利用闭包实现的私有属性存储器方法)

 function addPrivateProperty(o, name, predicate) {
            var value;
            o["get" + name] = function() { return value; };
            o["set" + name] = function(v) {
                if(predicate && !predicate(v)){
                    throw Error("set" + name + ": invalid value "+ v);
                }else {
                    value = v;
                }
            }
        }
        var o = {};
        addPrivateProperty(o, "Name", function(x) { return typeof x == "string"});
        o.setName("Frank");
        console.log(o.getName());
        o.setName(o);  //将抛出异常

这里除了调用o对象的setName()方法,且只能传入字符串对变量value进行赋值,同样只能通过o对象的geiName()方法才能访问到变量value的值。

4.闭包的危害

例5

        function constfunc(v) {return function() {return v;};}
        var funcs = [];
        for(var i = 0 ; i< 10; i++) {
            funcs[i] = constfunc(i);
        }
        console.log(funcs[5]());

        function constfuncs() {
            var funcs = [];
            for(var i = 0; i < 10; i++){
                funcs[i] = function() {
                    return i;
                }
            }
            return funcs;
        }
        var funcs2 = constfuncs();
        console.log(funcs2[5]());

第一个函数显然使我们需要的结果,可以第二次为什么会调用结果显示为10呢?其实数组funcs2储存的所有函数执行结果都是10。

第二个函数中创建了是个闭包,并将它们存储在一个数组中,这些闭包都是在同一个函数调用中定义的,因此它们可以共享变量i。当constfuncs()返回时,变量i的值是10,所有的闭包都共享这一个值,因此数组中的函数的返回值都是同一个值。

故而我们需要像第一个函数那样,不是直接将变量i返回而是通过传入参数的方法,实现变量私有化的方法来实现想要的结果。

猜你喜欢

转载自blog.csdn.net/djz917/article/details/84101313
今日推荐