通过实例彻底理解闭包

前言

在JS中,闭包和异步并称两大难点,那么闭包到底是什么,闭包有什么作用?接下来将通过几个案例来对闭包加以理解。

变量作用域

在讲闭包之前,先来看一下变量作用域。变量根据作用域的不同分为两种:全局变量和局部变量。主要有一下几点:

  1. 在函数内部变量的称为局部变量,在函数外部的则称为全局变量。
  2. 在函数内部可以使用全局变量,而在函数外部不能使用局部变量。
  3. 当函数执行完毕,本作用域内的局部变量会立即销毁

什么是闭包?

《JavaScript高级程序设计》中将闭包(closure)定义为:有权访问另一个函数作用域中变量的函数。即:闭包是函数

可以简单理解为: 若果一个函数内部有一个局部变量,别的全局作用域或局部作用域可以访问到这个变量,就会产生闭包,这个被访问局部变量所在的函数则称为闭包函数。

举个例子:

function fn() {
	var num = 10;
	function fun() {
		console.log(num);
	}
	fun();
}
fn();   //10

上述代码执行过程中,函数fun的作用域访问了另外一个函数fn作用域中的局部变量num,此时便产生了闭包。可以从chrom浏览器断点调试窗口看见生成的闭包fn 。
在这里插入图片描述
现在将以上代码稍作修改:

function fn() {
	var num = 10;
	return function() {
		console.log(num);
	}
}
var f = fn();   //10
f();

执行过程相当于:

var f = function() {
		console.log(num);
	}    //此实f是一个函数,里面存的是一个匿名函数。
f();  //10

上述执行过程中,在fn作用域之外的函数访问了fn作用域内的局部变量num,产生了闭包,闭包函数仍旧是fn。

闭包有什么作用?

正常情况下,一个函数内的变量只能在本函数内使用,且函数执行完毕后局部变量回立即销毁。
但在上述代码中,fn中的局部变量num在其作用域外部的f函数也可以使用,且fn()执行完毕后num并没有立即销毁,而是等f()也执行完毕后才销毁。
所以不难得出结论:闭包可以延伸变的量作用范围。

闭包案例

案例一:点击li标签输时输出当前li对应的索引值。

<nav>
	<ul>
	    <li>苹果</li>
	    <li>香蕉</li>
	    <li>葡萄</li>
	    <li>菠萝</li>
	</ul>
</nav>
<script>
	var lis = document.querySelectorAll('li');
	for(var i = 0; i < lis.length; i++) {
	            lis[i].onclick = function() {
	                console.log(i);
	            }
	        }
</script>

上述代码执行后,会发现无论点击那个li标签,输出的索引值均为4,这是因为onclick中的函数为异步任务,要点击时才会触发,而for循环执行时会直接跳过onclick中的函数。
现将其结合立即执行函数即可实现:

<script>
	for(var i = 0; i < lis.length; i++) {
	            (function(i) {
	                lis[i].onclick = function() {
	                console.log(i);
	            }
	            })(i);
	        }
</script>

在修改后的代码中,每次循环都会产生一个立即执行函数,点击后onclick中的函数访问立即执行函数接收的变量i,输出当前索引值,这一过程中便产生了闭包,可以推断这个闭包为立即执行函数。立即执行函数也称为小闭包,因为该函数内的任意一个函数都可以使用它传递的参数。
哈哈
但是会发现,上述代码在每次循环中都会产生一个立即执行函数,会降低效率,同时,立即执行函数中的变量i并没有在函数执行完毕后就立即销毁,而是要等onclick中的函数执行完毕后才会销毁,倘若一直不点击,则会导致内存泄露。使用ES6中新增的let属性实现该功能会更加理想。因此,在实际开发中要结合实际情况判断是否使用闭包。

案例二:点击li标签,5秒之后输出所有li元素

for (var i = 0; i < lis.length; i++) {
        (function(i){
            setTimeout(function () {
            console.log(lis[i].innerHTML);
        },5000);
        })(i);
    }

此处定时器中的回调函数与案例一中onclick中的回调函数均为异步任务,只有在触发了才会执行,即5秒钟之后才会执行,而for循环时同步任务,会立即执行。因此需要将其放入立即执行函数中。
这一过程中,定时器中的函数的作用域访问了立即执行函数中的i变量,产生了闭包。

思考题

可以思考一下以下代码的执行结果及其原因,并判断是否有闭包产生
1.

var name = 'this is window';
    var obj = {
        name: "this is  Obj",
        getNameFunc: function() {
            return function() {
                return this.name
            }
        }
    }
console.log(obj.getNameFunc()());     //this is window

提示: 全局变量时挂载在window下的,且匿名函数的this指向window

2.

var name = 'this is window';
var that;
    var obj = {
        name: "this is Obj",
        getNameFunc: function() {
            that = this;
            return function() {
                return that.name
            }
        }
    }
console.log(obj.getNameFunc()());     //this is Obj

总结:

  1. 闭包是函数。一个作用域可以访问另外一个函数的局部变量,被访问变量所在的函数即是闭包函数。
  2. 闭包延伸了变量作用域
  3. 开发中应结合实际情况决定是否使用闭包
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值