前言
对于 JavaScript 程序员来说,闭包(closure)是一个难懂又必须要知道的东西。其实闭包很easy,只是太过理论术语,感觉好像很复杂而已。下面就带着轻松的心态,来打败闭包吧。
什么是闭包?
简单一句话,当一个函数里面嵌套了一个函数的,并且内部函数用到了外部函数里的变量时,这个整体就形成了一个闭包结构。但主要是这个结构有啥特点?有啥用?
闭包的特点?
看一个例子:
var func = function(){
var a = 1;
return function(){
a++;
console.log ( a );
}
};
var f = func();
f(); // 输出:2
f(); // 输出:3
解析:
- 在上例中,func函数里面返回了一个函数,也可以说嵌套了一个子函数,内部函数里用到了外部函数的变量a.此时形成了闭包结构。
- 调用流程:首先f = func(),此时func()函数被调用,我们都知道函数的局部变量会随着函数调用的结束而被销毁。但是f()调用之后发现依然可以访a变量,说明局部变量a并没有被销毁。这就是闭包的一个特性:父函数的局部变量如果在子函数中有用到,那么这个局部变量就不会被销毁。就是子函数在对父函数的呼唤,你别销毁这个变量,我还要用呢。
OK,那么了解了闭包的特性,就得知道有啥用吧,不然学了就没啥意思了,接着往下看。
闭包的作用?
创建私有作用域
使用场景1:实现一个业务,有多个函数。
粗暴的写法:
var _count = 1;
var m1 = function(){
var b = _count + 1
};
var m2 = function(){
};
这种写法,就是什么东西都暴露在全局下了,变量_count也会很容易被篡改。好的写法,应该是,能让外界访问的就暴露给外界,不需要的外界就访问不了。
此时我们就可以采用闭包:如下
var module = (function(){
var _count = 0;
var m1 = function(){
};
var m2 = function(){
};
return {
m1 : m1,
m2 : m2
};
})();
module.m1();
这样 外界就访问不到module的局部变量了,对应的操作暴露出来,又可以访问。
使用场景2:点击对应节点,弹出对应的位置索引。
如果不经思考的话,你可能写出如下的代码,但实际上这样写,不管点哪个都会弹出2
var nodes = document.getElementsByTagName( 'div' );
for ( var i = 0, i < 2; i++ ){
nodes[ i ].onclick = function(){
alert ( i );
}
};
解析: 首先,执行循环,每次循环都声明一个点击是事件,如下:
第一次循环
nodes[0].onclick = function(){
alert ( i );
}
第二次循环
nodes[1].onclick = function(){
alert ( i );
}
解析:首先,我们要知道在js语言中,for循环不是一个代码块,也就是说没有形成一个单独的作用域,i在全局作用域都可以被访问,所以,执行完循环后,i的值已经变成5.而事件只有在点击的时候才执行内部的代码,当我们点击的时候,此时弹出的i已经是循环过后的i了,就是5了。
那怎么样才能实现我们要的效果呢?既然想弹出对应的i,我们在每次循环的时候,是不是就应该传入一个唯一的i,也就是每个i都有自己的作用域,不会被覆盖。 想到作用域,那自然是想到函数了,所以我们就在点击事件外,包裹一个函数,把每次循环的i当做参数传入进去, 此时参数是局部变量,如下:
var nodes = document.getElementsByTagName( 'div' );
for ( var i = 0, i < 2; i++ ){
(function(j){
nodes[ j ].onclick = function(){
alert ( j );
}
})(i)
};
解析: 其实此时,是不是形成了一个闭包结构了,立即执行函数内包裹了点击事件函数,且点击事件函数,用到了外层函数的变量j。所以这个j是不会被销毁的,所以在点击的时候,我们自然就能访问到对应函数的这个参数j了.
当然,也有很多种方法可以实现这个需求,因为今天讲的是闭包,就不说别的了。
总结
闭包的形成条件
- 函数嵌套函数
- 内部的函数引用了外部函数的参数和变量
闭包的特点
- 外部函数中,被内部函数引用的参数和变量不会被销毁
如有错误,欢迎大家纠正