js的闭包比较抽象,主要涉及到js的几个其他的特性:作用域链,垃圾回收机制,函数嵌套,等等。
首先,来理解一下作用域链。所谓作用域链,就是寻找使用到的变量的索引。其内部规则为:函数自身变量放在最前边,把父级函数中的变量放在其次,以此类推到全局变量为止。当函数中需要查询一个变量的值时,js会沿着作用域链查找,一旦找到,不再继续;如果没有找到,返回undefined。
其次,理解一下js的内存回收机制。一般情况下,一个函数在执行时会为其定义的变量划分内存空间,当函数执行完,这些变量就会被回收。但是存在一个函数中嵌套另一个函数的情况,内部函数可能会调用外层函数的变量,这时,外层函数的变量就不能被js回收,所以js解释器在遇到函数定义的时候,会自动把函数和他所需要的外部变量一起保存起来。也就是构建一个闭包。
下面通过一个例子理解闭包的作用:
我们知道,函数内部的局部变量无法在外部访问,那么怎样才能访问到函数内部的局部变量呢。可以在函数内部嵌套函数,然后把嵌套函数返回。这样,我们就能拿到函数内部的局部变量了。
function fun(){
var value = "inner";
function func(){
console.log(value);
}
return func();
}
fun();
以上得代码,其实就是闭包。本质上,闭包就是将函数内部和函数外部连接起来的桥梁。
以下是闭包的另一个例子:
var result = new Array();
function foo(){
for (var i = 0 ; i < 3 ; i++ ){
result[i] = (function(i){
return function () {
console.log(i);
}
})(i);
}
}
测试
foo();
result[0]();//0
result[1]();//1
result[2]();//2
如果不使用闭包,输出都是3.
下面总结一个闭包的作用:
1 匿名自动执行函数
很多情况下,函数只要执行一次,比如界面初始化。这时,就可以使用闭包,一方面,局部变量很快会被释放,另一方面,不会污染全局变量。
(function(){
for (var j = 0 ; j < 5 ; j++){
console.log(j);
}
})();
console.log(j);//j不会获取到
2 面向对象,模拟类,封装
function People(){
var username ;
this.getUsername = function(){
return username;
}
this.setUsername = function(name){
username = name ;
}
}
var p = new People();
p.setUsername("zhangsan");
console.log(p.getUsername()); //zhangsan
使用闭包的注意点:
1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。
解决方法是,在退出函数之前,将不使用的局部变量全部删除。
2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心。
最后,写一道思考题看看你是否理解了闭包:
var name = "Outer";
var object = {
name: "Inner" ,
getName: function(){
return this.name;
};
};
console.log(object.getName());
var name = "Outer";
var object = {
name: "Inner" ,
getName: function(){
return function(){
return name;
};
};
};
console.log(object.getName()());
两者有什么区别?
第一个输出:Inner
第二个输出:Outer