JavaScript中的闭包函数

3 篇文章 0 订阅
1 篇文章 0 订阅

JavaScript中的闭包函数

闭包是一个可以访问外部作用域的内部函数,即使这个外部作用域已经执行结束。

原理:利用作用域的嵌套,触发计算机的垃圾回收机制,将原本要删除的变量暂时保存起来,可继续使用。

以下为一些闭包的作用域以及应用:

作用域

作用域决定这个变量的生命周期及其可见性。 当我们创建了一个函数或者 {} 块,就会生成一个新的作用域。需要注意的是,通过 var 创建的变量只有函数作用域,而通过 let 和 const 创建的变量既有函数作用域,也有块作用域。

嵌套作用域

在 Javascript 中函数里面可以嵌套函数,如下:

(function fn(){
    var x = 1;
    function fn1(){ 
       console.log(x); 
    }
    fn1();
})();

fn1() 即是一个嵌套在 fn() 函数里面的函数。在 fn1() 函数里面可以通过外部函数访问到变量 x。此时,fn1() 函数就是一个闭包。

闭包就是内部函数,我们可以通过在一个函数内部或者 {} 块里面定义一个函数来创建闭包。

外部函数作用域

内部函数可以访问外部函数中定义的变量,即使外部函数已经执行完毕。如下:

(function fn(){
    var x = 1;
    setTimeout(function fn1(){
      console.log(x);
    }, 1000);
})();

并且,内部函数还可以访问外部函数中定义的形参,如下:

(function fn(p){
    var x = 1;
    setTimeout(function fn1(){
      console.log(x);//1
      console.log(p);//10
    }, 1000);
})(10);
外部块作用域

内部函数可以访问外部块中定义的变量,即使外部块已执行完毕,如下:

{
    let x = 1;
    setTimeout(function fn(){
      console.log(x);
    }, 1000);
}
作用域链

每一个作用域都有对其父作用域的引用。当我们使用一个变量的时候,Javascript引擎 会通过变量名在当前作用域查找,若没有查找到,会一直沿着作用域链一直向上查找,直到 global 全局作用域。

示例如下:

let x0 = 0;
(function fn1(){
	let x1 = 1; 
	(function fn2(){
		let x2 = 2;
  		(function fn3(){
    		let x3 = 3; 
     		console.log(x0 + " " + x1 + " " + x2 + " " + x3);//0 1 2 3
    	})();
  	})();
})();

我们可以看到,fn3() 这个内部函数可以访问其自身局部变量 x3 ,也可以访问外部作用域中的 x1 和 x2 变量,以及全局作用域中的 x0 变量。即:闭包可以访问其外部(父)作用域中的定义的所有变量。

外部作用域执行完毕后

当外部作用域执行完毕后,内部函数还存活(仍在其他地方被引用)时,闭包才真正发挥其作用。譬如以下几种情况:

  • 在异步任务例如 timer 定时器,事件处理,Ajax 请求中被作为回调
  • 被外部函数作为返回结果返回,或者返回结果对象中引用该内部函数

考虑如下的几个示例:

Timer
(function fn(){
    let x = 1;
    setTimeout(function fn1(){
      console.log(x);
    }, 1000);
})();

变量 x 将一直存活着直到定时器的回调执行或者 clearTimeout() 被调用。
如果这里使用的是 setInterval() ,那么变量 x 将一直存活到 clearInterval() 被调用。

Event
(function fn(){
   let x = 1;
   $("#btn").on("click", function fn1(){
     console.log(x);
   });
})();

当变量 x 在事件处理函数中被使用时,它将一直存活直到该事件处理函数被移除。

Ajax
(function fn(){
    let x = 1;
    fetch("http://").then(function fn1(){
      console.log(x);
    });
})();

变量 x 将一直存活到接收到后端返回结果,回调函数被执行。
在已上几个示例中,我们可以看到,fn1() 函数在父函数执行完毕后还一直存活着,fn1() 函数就是一个闭包。

除了 timer 定时器,事件处理,Ajax 请求等比较常见的异步任务,还有其他的一些异步 API 比如 HTML5 Geolocation,WebSockets , requestAnimationFrame()也将使用到闭包的这一特性。

变量的生命周期取决于闭包的生命周期。被闭包引用的外部作用域中的变量将一直存活直到闭包函数被销毁。如果一个变量被多个闭包所引用,那么直到所有的闭包被垃圾回收后,该变量才会被销毁。

闭包与循环

闭包只存储外部变量的引用,而不会拷贝这些外部变量的值。
查看如下示例:

function initEvents(){
  	for(var i=1; i<=3; i++){
    	$("#btn" + i).click(function showNumber(){
      		alert(i);//4
    	});
 	}
}
initEvents();

在这个示例中,我们创建了3个闭包,皆引用了同一个变量 i,且这三个闭包都是事件处理函数。由于变量 i 随着循环自增,因此最终输出的都是同样的值。

修复这个问题最简单的方法是在 for 语句块中使用 let 变量声明,这将在每次循环中为 for 语句块创建一个新的局部变量。如下:

function initEvents(){
 	for(let i=1; i<=3; i++){
   		$("#btn" + i).click(function showNumber(){
      		alert(i);//1 2 3
    	});
  	}
}
initEvents();

但是,如果变量声明在 for 语句块之外的话,即使用了 let 变量声明,所有的闭包还是会引用同一个变量,最终输出的还是同一个值。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值