闭包

闭包

闭包理解

** 特性:

函数嵌套函数;
函数内部可以引用函数外部的参数和变量;
函数变量和参数不会被垃圾回收机制回收;

**优缺点:

优点

  • 希望一个变量长期驻扎在内存中
  • 避免全局变量的污染
  • 私有成员的存在
    缺点
  • 常驻内存,增大内存使用量,使用不当回造成内存泄漏;

** 自执行函数的好处

隔离作用域,避免全局作用域污染
模拟块级作用域

应用的两种情况:函数作为返回值、函数作为参数传递。

第一,函数作为返回值:

1
2
3
4
5
6
7
8
9
10
function fn(){
var max = 10;
return function bar(x){
if (x>max){
console.log(x)
}
}
}
var f1 = fn();
f1(15);

如上代码,bar函数作为返回值,赋值给f1变量。执行f1(15)时,用到了fn作用域下的max变量的值

第二,函数作为参数被传递

1
2
3
4
5
6
7
8
9
10
var max = 10,
fn = function(x){
if(x>max){
console.log(x)
}
};
(function(f){
var max = 100;
f(15);
})(fn)

如上代码中,fn函数作为一个参数被传递进入另一个函数,赋值给f参数。执行f(15)时,max变量的取值是10,而不是100。

闭包与作用域

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

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

例一

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

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

1
2
3
4
5
6
7
8
9
10
11
12
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 关键字声明的局部变量来说,当退出函数时,这些局部变量即失去了 它们的价值,它们都会随着函数的调用的结束而销毁

例一

1
2
3
4
5
6
7
8
9
10
11
12
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,以此类。

1
2
3
4
5
6
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>

1
2
3
4
5
6
7
for(var i=0,len=nodes.length;i<len;i++){ 
(function( i ){
nodes[ i ].onclick = function(){
console.log(i);
}
})(i)
};

例三

1
2
3
4
5
6
7
8
9
10
11
12
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

图解