闭包的详解及常用使用场景

时间:2025-01-18 20:42:04

什么是闭包?

  • 当函数可以记住并访问所在的词法作用域是,就产生了闭包,即使函数是在当前词法作用域之外执行。

这句话怎么解释呢?

  • 在一个函数里面,有具体的属性,有具体的值,在当前函数作用域下,调用是没有问题的。但是要在其他地方调用这个函数里面的值是不行的。
    我们来看一段代码
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)
}