什么是闭包?
- 当函数可以记住并访问所在的词法作用域是,就产生了闭包,即使函数是在当前词法作用域之外执行。
这句话怎么解释呢?
- 在一个函数里面,有具体的属性,有具体的值,在当前函数作用域下,调用是没有问题的。但是要在其他地方调用这个函数里面的值是不行的。
我们来看一段代码
function fn1(){
var name='curslor'
function fn2(){
console.log(name)
}
fn2();
}
fn1();
其实上面的代码还可以修改一下:
function fn1(){
var name="curslor"
function fn2(){
console.log(name)
}
retrun fn2;
}
var fn3=fn1();
fn3();
在代码的最后,我们都可以拿到想要的值name。
- fn2在fn1中,也就是fn2可以访问fn1的作用域
- fn1执行后,将fn2的引用赋值给fn3
- 执行fn3,输出变量name
我们知道通过引用的关系,fn3就是fn2函数本身。执行fn3能正常输出name,这不就是fn2能记住并访问它所在的词法作用域,而且fn2函数的运行还是在当前词法作用域之外了。
正常来说,当fn1函数执行完毕之后,其作用域是会被销毁的,然后垃圾回收器会释放那段内存空间。而闭包却很神奇的将fn1的作用域存活了下来,fn2依然持有该作用域的引用,这个引用就是闭包。
总结:某个函数在定义时的词法作用域之外的地方被调用,闭包可以使该函数极限访问定义时的词法作用域。
经典例子:
for(var i=1; i<=10,i++){
setTimeout(function(){
console.log(i)
},1000)
}
输出结果是10个11
为什么?
究其原因:i是声明在全局作用中的,定时器中的匿名函数也是执行在全局作用域中,所以1秒之后,i的值已经发生了改变,每次都输出11了。
此时,我们就可以使用闭包了。为什么?因为,当函数可以记住并访问所在的词法作用域是,就产生了闭包,即使函数是在当前词法作用域之外执行。我们可以在i每次迭代的时候产生一个私有的作用域,在这个私有域中保存当前i的值。这就符合了闭包的思想
,也明白了闭包的使用。
下面我们来修改一下代码,使他正确输出
for(var i=1;i<=10;i++){
(function(j){
setTimeout(function(){
console.log(j);
},1000);
})(i)
}
闭包的应用比较典型是定义模块,我们将操作函数暴露给外部,而细节隐藏在模块内部:
function array() {
var arr = [];
this.add = function(val) {
if (typeof val == 'number') {
arr.push(val);
}
}
this.get = function(index) {
if (index < arr.length) {
return arr[index]
} else {
return null;
}
}
}
var arr=new array();
arr.add(1);
arr.add(2);
arr.add('xxx');
console.log(arr.get(1));
console.log(arr.get(2));
以上就是闭包的实例使用。
- arr引用array()
- arr调用相关函数 获取值
- 发现arr[]的值没有被销毁,且下次使用的时候,会保留上一次的数据
使用场景:
原生的setTimeout传递的第一个函数不能带参数,通过闭包可以实现传参效果。
function f1(a) {
function f2() {
console.log(a);
}
return f2;
}
var fun = f1(1);
setTimeout(fun,1000);//一秒之后打印出1
2.封装私有变量
function Animal(value){
var animal=value;
var setNum=0;
this.getName=function(){
return animal;
}
this.setName=function(value){
setNum++
animal=value
}
this.getNum=function(){
return setNum
}
}
var animal=new Animal("ni")
animal.setName("ho")
console.log(animal.getName())
console.log (animal.getNum())
3.防抖、节流
这里就不在详述,具体代码可以参考 防抖(debounce) 和 节流(throttling)
4.为元素的伪数组添加事件
// DOM操作
let li = document.querySelectorAll('li');
for(var i = 0; i < li.length; i++) {
(function(i){
li[i].onclick = function() {
alert(i);
}
})(i)
}