JavaScript 闭包系列一

一. 闭包的概念 

闭包是有权访问另外一个函数做用域中的变量的函数javascript

以下代码:根据变量做用域,函数outer中全部的局部变量对函数inner都是可见的。可是反过来不行,inner内部的局部变量对outer是不可见的。这就是JavaScript语言特有的链式做用域结构(chain scope),子对象会一级一级向上寻找全部父对象的变量。因此父对象的全部变量对子对象都是可见的,反之则不成立。html

function outer() {
     var i = 100;
     function inner() {
        console.log(i);    //100
    }
    inner();
}
outer();

既然函数inner能读取函数outer的局部变量,那么只要将inner做为返回值,就能够直接在outer外部读取它内部的变量。java

function outer() {
     var i = 100;
     function inner() {
        console.log(i);
    }
     return inner;
}
var res = outer();
res();    // 100

这个函数有两个特色:数组

1)函数inner嵌套在函数outer内部闭包

2)函数outer返回函数inner函数

执行完var rs = outer()后,实际rs指向函数inner。这段代码其实就是一个闭包。也就是说当函数outer内的函数inner函数被函数outer外的一个变量引用的时候,就建立了一个闭包。this

 

二. 闭包的做用

function outer() {
     var i = 100;
     function inner() {
        console.log(i++);
    }
     return inner;
}
var rs = outer();
rs();    // 100
rs();    // 101
rs();    // 102

上面的代码中,rs是闭包inner函数。rs共运行了3次,第一次100,第二次101,第三次102,这说明函数outer中的局部变量i一直保存在内存中,并无在调用后清除。spa

闭包的做用:在outer执行完毕并返回后,闭包是JavaScript的垃圾回收机制(garbage collection)不会回收outer所占的内存,由于outer函数内部的inner函数执行要依赖outer中的变量。(另外一种解释:outer是inner的父函数,inner被赋给了一个全局变量,致使inner会一直在内存中,而inner的存在依赖于outer,所以outer也一直存在于内存中,不会在调用结束后被垃圾回收机制(garbage collection)回收。) code

由上述可得知:htm

1)闭包有权访问函数内部的全部变量;

2)当函数返回一个闭包时,这个函数的做用域会一直在内存中保存直到闭包不存在为止。 

 

三. 闭包与变量 

做用链机制,闭包容许内层函数引用父函数中的变量,但该变量是最终值

var f =  function() {
     var rs = [];
     for( var i=0; i<10; i++) {
        rs[i] =  function() {
             return i;
        }
    }
     return rs;
};
var fn = f();
for( var i=0; i<fn.length; i++) {
    console.log("函数fn(" + i + ")返回值是:" + fn[i]());
}

上述代码执行后,fn为一个数组,表面上看,每一个函数执行后应该返回本身的索引值,但实际上,每一个函数都返回10。fn数组中每一个函数的做用域链上都保存着函数f的活动对象,它们引用的是同一变量i。当函数f返回后,i的值为10。

强制建立另外一个闭包让函数的行为符合预期,以下代码所示。

var f =  function() {
     var rs = [];
     for( var i=0; i<10; i++) {
        rs[i] = ( function(num) {
             return  function() {
                 return num;
            }
        })(i);
    }
     return rs;
};
var fn = f();
for( var i=0; i<fn.length; i++) {
    console.log("函数fn(" + i + ")返回值是:" + fn[i]());
}

这个版本中,没有直接将闭包赋值给数组,而是定义了一个匿名函数,并将当即执行匿名函数的结果赋值给数组。这里的匿名函数有一个参数num,在调用每一个函数时,传入变量i,因为参数是按值传递的,所以将变量i复制给参数num。而在这个匿名函数内部又建立并返回了一个访问num的闭包,所以rs数组中每一个函数包含本身的num变量,所以就能够返回不一样的数值。

 

四. 闭包中this对象

var name = "Jack";
var o = {
    name: "Ting",
    getName:  function() {
         return  function() {
             return  this.name;
        }
    }
};
console.log(o.getName()());    // Jack

var name = "Jack";
var o = {
    name: "Ting",
    getName:  function() {
         var self =  this;
         return  function() {
             return self.name;
        }
    }
};
console.log(o.getName()());    // Ting

 

五. 内存泄露

以下代码:建立了做为el元素事件处理程序的闭包,而这个闭包又建立了循环引用,只要匿名函数存在,el的引用数至少为1,所以它所占用的内存就永远不会被回收。

function assignHandler() {
     var el = document.getElementById('demo');
    el.onclick =  function() {
        console.log(el.id);
    }
}
assignHandler();

以下代码:把变量el设置为null,可以解除对DOM对象的引用,确保正常回收其占用内存。

function assignHandler() {
     var el = document.getElementById('demo');
     var id = el.id;
    el.onclick =  function() {
        console.log(id);
    }
    el =  null;
}
assignHandler();

 

六. 模仿块级做用域

任何一对花括号中的语句集都属于一个块,在这之中定义的全部变量在代码块外都是不可见的,称之为块级做用域。

( function() {
     // 块级做用域
})();


七. 闭包的应用

1)读取函数内部的变量

如上述例子中, 函数inner可以访问函数outer中的变量,将函数inner做为返回值,就能直接在outer外部读取它的变量。

2)在内存中维持变量

如上述例子中,因为闭包,函数outer一直存在于内存中,所以每次执行rs(),都会给i加1。

 

 

 

时间:2014-10-23

地点:合肥

引用:http://wlog.cn/javascript/javascript-closures.html

相关文章
相关标签/搜索