0%
3245 字 --- 耗时5 分钟.

JS 进阶之路 : 闭包

函数在 JavaScript 中是第一型对象,而闭包的使用使其变得非常灵活。闭包也算是 JS 中非常有特色的,好吧,先来说一下闭包的好处与缺点吧:

使用闭包主要是为了设计私有的方法和变量。闭包的优点是可以避免全局变量的污染,缺点是闭包会常驻内存,会增大内存使用量,使用不当很容易造成内存泄露。

简单点说,闭包是一个作用域,这个作用域的对象是函数,即函数在创建时允许该自身函数访问并操作自身函数之外的变量,当然,对于自身函数内的变量必然是可以访问的。换句话说,闭包可以让函数访问所有变量和函数,只要这些变量和函数存在于该函数声明时的作用域内进行。

比如下面这个例子:

//全局可以访问
var outValue = 'outValue';  
var fn;  
function outFunction(){  
    //内部也可以访问
    var innerValue = 'innerValue';
    function innerFunction(){
        console.log(outValue); //'outValue'
        console.log(innerValue); //'innerValue'
        console.log(laterValue); //'laterValue'
    }
    fn = innerFunction;
}
outFunction();  
console.log(laterValue); //'undefined'  
//函数执行前申请的变量也可以访问
var laterValue = 'laterValue';  
fn();  

上面的例子可以说明三个事实:

  1. 同一个作用域内,未赋值的变量不可访问(这和函数全作用域调用有所不同)
  2. 内部函数变量可以在闭包中使用
  3. 如果变量在函数执行前申明(比如laterValue),作用域之外的变量也可以访问

变量的提前申明,如下,

console.log(hello) //'undefined'  
var hello = 'hello';  

实际上,变量是提前申明,在闭包开始的时候,就已经申明变量。

var hello;  
console.log(hello) //'undefined'  
hello = 'hello';  

用闭包实现访问私有变量

闭包用来封装私有变量,当然,也可以构造一些方法来访问改变这些私有变量,这些方法和 Java 中的 set、get 原理是一样的。

function fn(){  
    var counts = 0;
    this.getCounts = function(){
        return counts;
    };
    this.addCounts = function(){
        counts++;
    };
}
var test = new fn();  
test.addCounts();  
console.log(test.getCounts()); //'1'  

counts 的值变成了 1 ,可以通过 fn 提供的 getCounts()来访问。

对于var test = new fn();,对 test 做了如下操作:

var test = {};  
test.__proto__ = fn.prototype;  
fn.call(test);  

如果 fn有参数的话,还需要把参数放到 call 函数里。至于__proto__,是有关于 JS 的原型链,后面的文章会写到。

即时函数

即时函数也是常见的闭包使用,类似(function(){})(),有时候还用 ! 或 + 放到function(){}前面,组成!function(){}()

说到即时函数,使用最多的还是对循环进行处理,比如下面代码:

var buttons = document.getElementsByClassName('button');  
for(var i = 0; i < buttons.length; i++){  
    buttons[i].addEventListener('click', function(){
        alert(i);
    });
}

上面的代码点击 button,都只会弹出同一个 i 值,就是 i 的最终状态,即 i 的值等于buttons.length

这个原因很好理解,这跟前面说的一样,就是 button 点击之后,函数才会执行,如果把执行函数当作闭包的话,i 是外部变量,闭包记住的都是变量的引用,而不是函数执行时的变量状态,所有访问变量 i 的都是 i 的最终状态。

用即时函数来解决:

var buttons = document.getElementsByClassName('button');  
for(var i = 0; i < buttons.length; i++){  
    !function(now_i){ //用即时函数把 i 赋值给 now_i
        buttons[now_i].addEventListener('click', function(){
            // 访问的now_i是闭包内的变量
            alert(now_i);
        });
    }(i);
}

即时函数也是闭包,执行的时候,会把当前的 i 值传递给当前闭包 now_i,当按钮被点击的时候,函数访问的不是最终的 i,而是当前闭包内的 now_i 值。这算是闭包一个非常有用的使用。

这篇文章应该早点写的,最近真的太忙了,近期还会写一篇关于 JS 中原型链的,共勉。