闭包详解

2,742 阅读5分钟
原文链接: www.yaya12.com

闭包的形成跟变量的作用域以及变量的生存周期密切先关。

一、变量的作用域,是指变量的有效范围

当在函数中声明一个变量的时候,如果该变量前面没有带上关键字 var,这个变量就会成为全局变量 ,这当然是一种很容易造成命名冲突的做法。
另外一种情况是用 var 关键字在函数中声明变量,这时候的变量即是局部变量,只有在该函数内部才能访问到这个变量,在函数外面是访问不到的。

例一

var func=function(){
    var a=1;
    console.log(a); //输出:1 
};
func();
console.log ( a ); // Uncaught ReferenceError: a is not defined

例二,变量的搜索是从内到外而非从外到 内的。

var a=1;
var func1 = function(){ 
    var b=2;
    var func2 = function(){ 
        var c=3;
        console.log ( b ); // 输出:2 
        console.log ( a );// 输出:1
    }
    func2();
    console.log(c);//输出:Uncaught ReferenceError: c is not defined
}; 
func1();

二、变量的生存周期。

对于全局变量来说,全局变量的生存周期当然是的永久,除非我们主动销毁这个全局变量。

而对于在函数内用 var 关键字声明的局部变量来说,当退出函数时,这些局部变量即失去了 它们的价值,它们都会随着函数的调用的结束而销毁

例一

var func = function(){ 
    var a=1;
    return function(){ 
        a++;
        console.log(a);
    } 
};
var f=func();
f();// 输出:2 
f();// 输出:3
f();// 输出:4
f();// 输出:5

跟我们之前的结论相反,上面的例子在当退出函数后,局部变量 a 并没有消失,而是似乎一直在某个地方 存活着。

这是因为当执行 var f = func();时,f 返回了一个名函数的引用,它可以问到 func() 被调用时产生的环境,而局部变量 a 一直处在这个环境里。

既然外局部变量所在的环境还能被外 界访问,这个局部变量就有了不被销毁的理由。在这里生了一个闭包结构,局部变量的声明看起来被延续了。

例二,假设页面上有 5 个 div 节点,我们通过循环来给每个 div绑定 onclick 事件,按照索引顺序,点击第 1 个 div 时弹出 0,点击第 2 个 div 时出 1,以此类。

var nodes = document.getElementsByTagName( 'div' );
for(var i=0,len=nodes.length;i<len;i++){ 
    nodes[ i ].onclick = function(){
        alert(i); 
    }
};

测试这段代码会发现,无论点击哪个 div,最后弹出的结果都是 5。

这是因为 div 节点的 onclick 事件是被异步触发的,当事件被触发的时候,for循环早已结束,此时 i 的值已经是 5,

所以在 div 的 onclick 事件函数中顺着作用域链从内到外查找变量 i 时,查找到的值总是 5。

解决方法是在闭包的帮助下,每次循环的 i 值都封闭起来。当在事件函数中顺着作用域链从内到外查找变量 i 时,会先找到被封闭在闭包环境中的 i,如果有 5 个 div,这里的 i 分别 是 0,1,2,3,4

for(var i=0,len=nodes.length;i<len;i++){ 
    (function( i ){
        nodes[ i ].onclick = function(){ 
            console.log(i);
        } 
    })(i)
};

例三

var Type = {};
for ( var i = 0, type; type = [ 'String', 'Array', 'Number' ][ i++ ]; ){ 
    (function( type ){
        Type[ 'is' + type ] = function( obj ){
            return Object.prototype.toString.call( obj ) === '[object '+ type +']';
        }
    })( type )
};
console.log( Type.isArray( [] ) );// 输出:true
console.log( Type.isString( "str" ) );// 输出:true
console.log( Type.isNumber( 5  ) );// 输出:true
console.log( Type.isString( [] ) );// 输出:false

三、闭包的作用

1、封装变量—-闭包可以帮助一些不需要暴露在全局的变量封装成“私有变量”。

例一,计算乘积

var mult = function(){ 
    var a=1;
    for(var i=0,l=arguments.length;i<l;i++){ 
        a = a * arguments[i];
    }
    return a; 
};

现在我们觉得对于那些相同 的参数来说,每次都进行计算是一种浪费,我们可以加入缓存机制来提高这个函数的性能

var cache = {};
var mult = function(){
    var args = Array.prototype.join.call( arguments, ',' ); 
    if ( cache[ args ] ){
        console.log('从cache中读取'+cache[ args ]);
        return cache[ args ]; 
    }
    var a=1; 
    for(var i=0,l=arguments.length;i<l;i++){
        a = a * arguments[i]; 
    }
    return cache[ args ] = a; 
};
console.log ( mult( 1,2,3 ) ); // 输出:6
console.log ( mult( 1,2,3 ) );// 输出:从cache中读取6 6
console.log ( mult( 1,2,3,4 ) );// 输出:24

代码优化我们看到 cache 这个变量仅仅在 mult 函数中被使用,与其让 cache变量跟 mult 函数一起平行 地在暴露在全局作用域下,不如把它封闭在 mult 函数内部,

这样可以减少页面中的全局变量,以 避免这个变量在其地方被不小心修改而引发错误。

var mult = (function(){
    var cache = {};
    return function(){
        var args = Array.prototype.join.call( arguments, ',' ); 
        if ( args in cache ){
            console.log('从cache中读取'+cache[ args ]);
            return cache[ args ]; 
        }
        var a=1; 
        for(var i=0,l=arguments.length;i<l;i++){
            a = a * arguments[i]; 
        }
        return cache[ args ] = a; 
    }    
})();
console.log ( mult( 1,2,3 ) ); // 输出:6
console.log ( mult( 1,2,3 ) );// 输出:从cache中读取6 6
console.log ( mult( 1,2,3,4 ) );// 输出:24

我们对上面的代码做一下优化,对有可能修改或者通用的部分进行提炼

var mult = (function(){
    var cache = {};
    var calculate=function(){
        var a=1; 
        for(var i=0,l=arguments.length;i<l;i++){
            a = a * arguments[i]; 
        }
        return  a; 
    }
    return function(){
        var args = Array.prototype.join.call( arguments, ',' ); 
        if ( args in cache ){
            console.log('从cache中读取'+cache[ args ]);
            return cache[ args ]; 
        }
        return cache[ args ] = calculate.apply( null, arguments );
    }    
})();
console.log ( mult( 1,2,3 ) ); // 输出:6
console.log ( mult( 1,2,3 ) );// 输出:从cache中读取6 6
console.log ( mult( 1,2,3,4 ) );// 输出:24

2、延续局部变量的寿命。

img 对象经常用于进行数据上报,如下所示

var report = function( src ){ 
    var img = new Image(); 
    img.src = src;
};
report( 'http://xxx.com/getUserInfo' );

因为一些低版本浏览器的实现存在 bug,在这些 浏览器下使用 report 函数进行数据上报会丢失 30%左右的数据。

丢失数据的原因是img是report函数中的局部变量,当report函数的 调用结束后,img 局部变量随即被销毁,而此时或许还没来及发出 HTTP 请求,所以此次请求数据就会丢失。

var report = (function(){ 
    var imgs = [];
    return function( src ){
        var img = new Image(); 
        imgs.push( img ); 
        img.src = src;
    } 
})();

360云盘代码下载: yunpan.cn/ckcdbpdKVxM… (提取码:d894)