闭包
闭包初识
在MDN中闭包的定义为:可以访问自由变量的函数。(所以我们可以将所有的函数看做是闭包)
在我们平时的开发使用的我们可以对闭包定义为:可以在函数本身作用域以外的地方被调用。
首先我们应该先了解一个概念,JS的作用域是静态的,不管函数以哪种方式被调用都只访问申明函数时的作用域内的变量。
先看下面的一个小例子:
var a = "outer"; function print(){ console.log(a); }
print();
//毫无疑问打印-->outer
var a = "outer"; function print(){ console.log(a) } function log(){ var a = "inner"; print(); }
log();
//仍然会打印-->outer
var a = "outer"; function print(){ var a = "inner"; return function(){ console.log(a); } } var log = print(); log();
//打印-->inner
通过红色的定义我们已经知道函数在调用时,会访问声明函数时的作用域内的变量,所以我们第二个函数会输出outer第三个函数会输出inner。
首先说明第三个函数已经构造除了一个最简单的闭包。我们知道函数print形成了一个独立的块级作用域,里面的变量只能在函数内部使用,并且在正常的情况下,一旦函数调用完成就会回收里面声明的变量,我们在外层作用域不能访问到,但是在实际上我们在第三个函数中通过log函数额调用仍然输出了inner,这已经违背了我们一般情况,这种特殊的情况我们就称为闭包,更加通俗但不准备的定义--可以外层作用域访问到内层作用域的变量,着就是闭包。
闭包使用
闭包即一个封闭的空间,通过闭包我们形成一个独立的作用域,可以减少变量名称之间的污染。能形成独立作用域的我们最常见的就是函数,所以闭包和函数就是树和叶一样,其实是不分彼此的。而我们经常使用的一个闭包方式就是匿名函数自执行--(function(){--statement--}())
闭包的一个好处就是可以减少变量的污染,这个是显而易见的:
(function(){ var name = "zt"; console.log(name) }()) (function(){ var name = "xjj"; console.log(name); }())
我们对某一个模块的操作不会影响到另一个模块。
在我们平时的开发中同时会通过三种方式来使用闭包:
1.参数传递
2.return 一个基本类型或者对象
3.对对象的属性进行扩展
我们先看一个经典的闭包问题:
for(var i=0;i<5;i++){ setTimeout(function(){ console.log(i); },0) }
我们想打印出每次循环的i的值,而上面的这段代码显然是不能达到我们的预期的效果的,因为JS是一种运行在浏览器/node环境中一种单线程语言,只有主线程执行完毕之后,才会去执行消息队列中的语句,所以上面的代码我们可以理解为每一次的循环都会创建一个函数,这个函数的功能的输出i的值,只有循环执行完成以后才会调用函数,但是当调用函数的时候i的值已经变成了5,所以会输出5个5。
为了达到我们预期的效果我们可以使用闭包来完成:
for(var i=0;i<5;i++){ (function(i){ setTimeout(function(){ console.log(i) },0) }(i)) }
此时我们预期的效果已经实现-->0,1,2,3,4
每次循环创建的函数不再去访问我们的变量i而是去访问我们的参数i,每一个函数的参数都是我们当前循环的变量i的值,所以达到了我们预期的效果。
另一种我们使用的闭包的方式为 return 一个对象
var person = function(){ var current = new Date(); var year = current.getFullYear(); var age = year - 1993; return { name:"zt", age:age } }();
通过我们完成了一个操作-->声明一个对象var person = {name:"zt",age:age},通过person.age属性我们可以访问到我们函数内部的变量age的值,同时减轻了变量的污染
最重要的一种使用闭包的方式为,对某个对象进行属性扩展
var person = {}; (function(p){ function say(){ console.log("i can say"); } function sport(){ console.log("i like sport"); } p.say = say; p.sport = sport; }(person))
通过这种方式我们为person对象扩展了两个方法,我们经常使用的JQ插件就是通过这种方式来进行扩展的。