javascript闭包

闭包定义

MDN的官方解释:

一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包closure)。

 一般理解:在嵌套定义的函数中

闭包让你可以在一个子函数中访问到父函数的作用域。在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。

作用域

javascript的变量作用域分为两种类型

  1. 全局作用域:直接在script标签内部定义或函数内部未声明直接赋值的变量(就是没写var直接 变量名=值)。这类变量直接作为window对象的属性保存,可以被直接访问或者是被函数内部的语句访问。
  2. 函数作用域:定义在函数声明内部的变量。一般情况下不能被函数外的语句直接访问,只能被函数内部的语句访问。

作用域链

不同作用域相互链接成一个作用域链。

javascript在执行函数时会查找变量并对变量值进行处理。针对函数内部一句处理num1这个变量的语句,执行中num1的值会首先在函数对象中寻找,函数对象中没有就到函数对象的上一级对象中寻找num1这个变量,上一级对象中没有这个变量时就继续到上一级去寻找。一直寻找到window对象,如果还没有,就会抛出ReferenceError引用错误。

eg1:直接在script标签中定义的函数会先在函数内部寻找,没有再到window对象中(全局作用域)中寻找变量。

eg2:在某个自定义对象中定义的方法,寻找变量时会先在函数内部中寻找,没有就到对象中寻找,再没有就到自定义对象的上一级去寻找,上一级可能是window对象,再寻找不到就抛出错误,也可能是另一个对象,重复寻找这个过程一直持续到找到抛出错误。

闭包实例

var a=12;
function test (){
        var a=13;
		function f(){return a;}
		return f();
    }
console.log(test());

这里输出结果为13。

var a=12;
	function test (){
		var a=13;
		function f(){return a;}
		return f;
}
console.log(test()());

这里输出结果也是13。两次test函数的返回值不同,前者返回值直接是值13,后者返回值为f这个函数,函数再执行。这时f是在局部作用域定义的,执行时依然寻找局部作用域中的a返回,这就使我们可以在外部访问函数内部定义的变量。

闭包实现效果

闭包使得我们可以从外部访问函数内部的变量。使得函数内部变量不会在函数执行完毕后无法访问,被垃圾回收机制回收。使用闭包,我们可以实现

  • 外部访问函数内部变量。
  • 函数内部变量常驻内存。

实际开发中常用闭包实现for循环绑定事件。代码如下

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>Document</title>
	<style>
		li{
			width: 100px;
			height: 100px;
			background-color: lightblue;
			list-style: none;
			float: left;
		}
	</style>
</head>
<body>
	<li>1</li>
	<li>2</li>
	<li>3</li>
	<li>4</li>
	<li>5</li>
	<li>6</li>
	<li>7</li>
	<li>8</li>
	<li>9</li>
	<li>10</li>
	<script>
		var lis=document.getElementsByTagName("li");
		for(var i=0;i<lis.length;i++)
		{
			lis[i].onclick=function(){
				console.log(i+1);
			}
		}
	</script>
</body>
</html>

这里实际实现的效果是,点击任意li都会输出10,而不是对应的123······。以下介绍为什么会出现这种情况:

绑定点击事件的函数内部没有定义变量i,变量i是引用外部的for循环中的i。点击事件在for循环执行中被绑定到对应的li上,但是只是指定输出内容为i,点击事件触发时引用i的值,这时js代码已经执行完,for循环自然也已经结束,这时i已经是10,为每个li元素绑定的都是输出10的点击事件。

修正方案:

        var lis=document.getElementsByTagName("li");
		for(var i=0;i<lis.length;i++)
		{
			(function f(i){
				lis[i].onclick=function(){
				console.log(i+1);
			}
			})(i);
		}

 这里在for循环里立即执行一个函数,函数参数是i,执行后为函数绑定点击事件输出i。

使用闭包还可以封装对象的私有属性和方法。这里不做赘述,另有博文介绍。

底层细节

函数在调用时,会产生一个执行期上下文(active object)对象。函数拥有的变量都会变成这个对象的属性。每次调用该函数,都会创建一个这样的对象。这个对象在函数调用时创建,在函数执行结束后,如果没有方式可以再访问这个函数对象,这个函数对象就会被当作内存垃圾被清除。当函数内部定义一个嵌套函数并把这个函数对象返回到外部时,这个被返回的嵌套函数对象是在函数内部定义的,可以访问被嵌套函数外部的-函数内部的变量。我们通过返回这个嵌套函数来获得访问函数内部变量的可能性,从而使垃圾回收机制不能把执行期上下文这个对象回收(已经有访问的方式)。

值得注意

当内层函数保留了外层函数中变量的引用时,外层函数的变量就不再是无法被访问的变量,而是可以被内层函数调用并访问的变量,不会被javascript的垃圾回收机制清理掉。这样可能导致内存垃圾持续占用内存,导致内存泄漏。因此,在使用闭包的情况下需要确保内存泄漏的处理。可以在代码最后将变量的引用置为null,使内存垃圾无法被引用,进而被处理。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值