我所认识的闭包
什么是闭包
我们可以把js中的每一个函数理解成一个闭包。但通过函数之间的嵌套更能体现出闭包的特性。
var counter = function () { var count = 0; var add = function () { count ++ ; return count; } return add; } var Timer = counter(); setInterval(function(){ console.log(Timer())// 1 2 3 4 5…… },1000)
在浏览器中运行以上代码后,我们会看到以下现象:每隔一秒我们就会在控制台看到一个新增加的数字。
不妨让我们来解读上边的代码:变量count是在函数counter内部定义的,只有在函数counter调用时,变量count才有生命周期。当函数conter从调用栈中返回时,其内部变量count原先申请的内存空间也随之被释放。可问题是,在上述代码中,当counter调用结束后,执行Timer()时,变量count非但没销毁,而且随着Timer的每一次调用,都会增加,这是为什么呢?
这正是闭包的特性。在函数外部可以改变和引用函数内部的变量。当一个函数返回一个内部定义的函数时,就形成了闭包。在返回这个内部函数的时候,也会返回这个函数的定义环境。怎么来理解定义环境呢?
var counter = function () { var count = 0; var add = function () { count ++; return count; } return add; } var counter1 = counter(); var counter2 = counter(); counter1();//1 counter2();//1 counter1();//2 counter1();//3 counter2();//2
就像你看到的,couner1和counter2是分别调用counter()生成的2个不同的闭包实例,他们内部调用的变量count分别属于各自的运行环境!我们可以这么来理解:在调用counter()返回add函数时,也会把add函数调用的counter函数内部的所有用到的变量(比如:count)一块返回,并保存作为一个副本,每个闭包实例之间相互独立,互不影响。
相信接下来的这个例子,好多人是再熟悉不过了,不妨看看。
for(var i=0; i<10; i++){ var obj = document.createElement("div"); obj.innerHTML=i; obj.onclick = function(){ alert(i) } document.body.appendChild(obj); }
执行完之后,页面上会出现10个div,我们原本想让点击每一个div的时候,弹出显示对应div的索引,可是事与愿违,总是弹出10,为什么呢?这是因为在执行点击的时候,变量i已经累计到10了,所以总是弹出10,那么如何得到我们想要的效果呢?
for(var i=0; i<10; i++){ (function (n) { var obj = document.createElement("div"); obj.innerHTML=n; obj.onclick = function(){ alert(n) } document.body.appendChild(obj); })(i) }
闭包的用途
除了上述提到的闭包能在函数外部访问函数内部的变量,以及造成内存泄漏等特性外,外包主要在以下2个方面应用较多:
♣ 嵌套的回调函数
在函数式编程中,经常会遇到在一个业务执行完成之后吊起另一个业务的关系,就是我们所说的回调。那么闭包在回调的使用更加明显。
$.getJSON(url).done(function(res){ return function () { //处理返回结果 } }).fail(function () { //处理失败提示 })
♣ 隐藏对象的细节
通过上述提到的代码,不难看出,如果想访问counter内部的变量count,必须调用函数Timer()。那么受这个启发,我们也可以把对象封装起来,只返回一个”返回器“对象,以此来实现对对象属性的隐藏。
var privateObj = function (){ return function (){ return { name:"属性1", val:"值1" } } }
为什么要这么做呢?对象没有私有属性,也就是说对象的每一个属性都是曝露给外部的。这样可能会有安全隐患,譬如对象的使用者直接修改了某个属性,导致对象内部数据的一致性受到破坏等。