之前想不通的一道题 —— 主要是不知道思路怎么来的,为何会想到用闭包。本来打算就将原博客转载过来,但是刚刚重新审视这道题的时候,好像看到了以前没有发现的东西,有种恍然大悟的感觉,所以决定用自己的话来解释这道题的思路。
假如我们想制作一个计数器,每点击一次就加一,代码如下:
var counter = 0; //把计数器counter设置成全局变量
function add(){
return counter += 1;
}
add(); //1
add(); //2
add(); //此时counter=3
虽然可以实现功能,但问题就在于其他语句也有可能会改动到 counter ,这样的计数器是不安全的。
如果把 counter 改为外部访问不到的局部变量呢?
function add(){
var counter=0;
counter+=1;
}
add(); //counter为1
add(); //counter为1
add(); //counter为1
虽然保证了 counter 不会被其他语句影响到,但问题就在于每次调用函数都会重置 counter,无法实现计数功能。
所以我们需要的 counter 应该满足:1.不会被重置;2.在函数内部
第 2 点容易满足,但是由上面的例子我们知道,如果单纯把 counter 写在一个函数里,则每次调用都会重置,所以我们定义这样一个嵌套函数:将 counter 放在父函数里,子函数作为操作 counter 的函数,每次我们只调用子函数。
但是,全局作用域是无法访问嵌套函数中的子函数的,所以我们必须将子函数作为闭包返回出来,使其暴露在全局作用域下。依照这个想法,代码如下:
var add = function(){
var counter = 0;
return function(){
return (++counter);
}
}
add()(); //counter为1
add()(); //counter为1
add()(); //counter为1
这段代码的意思是,把 add 函数(父函数)执行后返回的函数(子函数)执行一次(注意这里是两次执行)。但这样的问题在于:每次调用 add()()
时依然执行了一次父函数,结果就是依然重置了 counter。
那么有没有办法让父函数只执行一次,仅在那一次初始化 counter,之后每次都只通过执行子函数来操作 counter 呢?
可以用自执行函数来解决这个问题 —— 也就是通过自执行函数(而不是通过 add()
)来调用父函数,在这一次调用初始化 counter,之后将返回的子函数赋给 add
,通过调用 add()
来操作 counter。
var add = (function(){
var counter = 0;
return function(){
return(++counter);
}
})();
//这里add已经是父函数的执行结果了,即add已经是返回的那个子函数了
add(); //counter为1
console.log(counter) //undefined
add(); //counter为2
console.log(counter) //undefined
add(); //counter为3
console.log(counter) //undefined
接下来就是闭包的知识了:
在每次调用闭包 add
—— 即 function(){return(++counter)
时,由于 add
中存在自由变量 counter,所以它必须到定义该函数时所在的那个作用域中去寻找该变量,也就是到父函数中去寻找。恰好父函数中有一个为 0 的 counter 可以被引用,所以这时候完成加一操作,counter 变成 1。注意,接下来我们尝试调用了 console.log(counter)
,但是输出的是 undefined
,这说明了即使 add
函数执行后返回了值为 1 的counter,但是该返回值并不是返回到全局作用域中(不然不会输出 undefined
),而是覆盖了父函数中原来定义的 counter,使 counter 变为1;第二次调用 add
函数依然同上,只是此时引用 counter 时,引用的是为 1 的 counter,加 1 后变为 2;同理第三次,引用的是为 2 的 counter,加 1 后变为 3。
基于这道题,我们不难看出使用闭包函数的好处:
缓存
最显而易见的好处,就是可以实现数据缓存,我们可以把一个需要长期用到的变量作为相对于闭包函数的自由变量,在闭包函数里直接使用它。因此该自由变量只初始化一次,但却可以通过多次调用闭包函数来使用。这比起我们直接在闭包函数中定义初始化变量,多次调用则多次初始化的做法,效率更高。闭包函数常见的一种用途就是上面例子中的 —— 实现计数功能。
封装
自由变量只能被闭包函数本身或者其子函数访问,而不能被闭包函数之外的函数访问。这就实现了面向对象的封装性,更安全更可靠。
参考:
http://www.cnblogs.com/haidaojiege/p/7070560.html
https://www.cnblogs.com/leoin2012/p/3978979.html