当前位置:主页 > 查看内容

理解闭包

发布时间:2021-06-04 00:00| 位朋友查看

简介:前言 闭包可以说是最最最最最最常见的面试题之一了很多人面试的时候都被问到过闭包的知识,闭包的概念是什么?闭包的好处和坏处是什么?闭包有哪些应用场景了? 前两天朋友面试的时候也被问到过这个问题网上的答案也有很多:有的说能够读取其他函数内部作用域的函……

前言

闭包可以说是最最最最最最常见的面试题之一了,很多人面试的时候都被问到过闭包的知识,闭包的概念是什么?闭包的好处和坏处是什么?闭包有哪些应用场景了?
前两天朋友面试的时候也被问到过这个问题,网上的答案也有很多:有的说能够读取其他函数内部作用域的函数就是闭包,也有的说所有的函数都是闭包,哪怕是全局作用域上的,因为它能获取到全局作用域上的变量…答案也是很不统一,这就让还是新手的我对于答案有点举棋不定了,于是重温了一下《你不知道的javascript上卷》和《javascript高级程序第四版》,看了一下这两本书对于闭包的解释,记录一下相关知识,希望能够帮助到大家。

初始闭包

《你不知道的javascript》 javascript中闭包无处不在,你只需要能够识别并拥抱它。 闭包就是基于词法作用域的,你在书写代码的时候产生的一种现象。
当函数能够记住并访问自己所在的词法作用域的时候就产生了闭包

function foo(){
	var a = 2;
	function bar(){
		console.log(a) //2
	}
}
foo()

上述的这段代码中,bar可以说也是一个闭包,因为它能够访问到他所在的作用域里面的变量。但是他却是封闭在foo里面的,就相当于一个词法作用域的查找规则而已,自己的作用域没找到,去上一个作用域找。也是闭包的一部分

function foo(){
	var a = 2;
	function bar(){
		console.log(a) 
	}
	return bar
}
var baz = foo()
baz() //2  -----------这才是闭包的效果

我们将bar函数传递出去,然后调用foo函数的时候赋值给baz,调用baz就相当于调用了内部的bar函数了,而且bar相当于在自己所在的词法作用域之外执行了。foo函数执行之后,正常的话内部的作用域会被销毁,垃圾回收机制也会释放不再使用的内存空间,但是闭包可以阻止这种事情,让foo内部的作用域依然存在着。这样以供bar函数在后面的任何地方,任何时间引用
bar函数依然持有对这个作用域的引用,这个引用就叫闭包。

无论我们以何种的方式将bar这个函数传递出去
function foo() {
	var a = 2;
	function bar() {
		console.log( a ); // 2
	}
	baz( bar);
}
function baz(fn) {
	fn(); // 作为参数传递出去 这也是闭包
}
var fn;
function foo() {
	var a = 2;
	function bar() {
		console.log( a );
	}
	fn = baz; // 直接赋值给全局变量fn
}
function bar() {
	fn(); // 这也是闭包
}
foo();
baz(); // 2
上面这几组代码,有将函数作为参数传递出去的,也有将函数赋值给一个全局变量的,都让这个函数可以在词法作用域之外调用了,都是闭包。包括我们平时使用的一些计时器,定时器,事件监听器这些使用了回调 函数,都是闭包的应用
function getName(name){
	setTimeout( function timer() {
		console.log(name);
	}, 1000 );
}
getName('小明')
//将一个内部函数timer传递给setTimeout,timer具有涵盖getName的闭包,还有这对name的引用

for循环与闭包

我们经常使用for循环
for (var i=1; i<=5; i++) {
	setTimeout( function timer() {
		console.log( i );
	}, i*1000 );
}

上述这段代码中,正常情况下我们想打印 1,2,3,4,5,但是事实上却以每秒一次的频率打印了五次6,6是从哪来的那?这个循环的终止条件是 i 不在<=5,所以首次成立条件的时候 i 就是6,打印的就是循环结束时候的值?这是为什么?
虽然这个timer函数在每次迭代的时候都定义一次,相当于定义了五个,但是都封闭在一个共享的全局作用域下面,就相当于只有一个i,根据js中setTimeout的运行机制来看,也是正常的。

那我们得如何处理那?我们可以在每次迭代的时候都创建一个作用域,这个样的话就不是在一个全局作用域了。
for (var i=1; i<=5; i++) {
	(function() {
		setTimeout( function timer() {
			console.log( i );
		}, i*1000 );
	})();
}

我们先尝试使用了一下自执行函数来创建作用域。
还是不行…

我们可以在每次迭代的时候创建一个块级作用域
for (let i=1; i<=5; i++) {
	setTimeout( function timer() {
		console.log( i );
	}, i*1000 );
}

我们使用到了es6 中的let定义变量,在每次迭代的时候都创建一个块级作用域,这样是可以的

闭包的应用场景

闭包有什么应用场景?比如定时器,回调函数,防抖函数....很多都是用到了闭包
我们还可以封装私有变量
(function() {
	// 私有变量和私有函数
	let privateVariable = 10;
	function privateFunction() {
		return false;
	}
	// 构造函数
	MyObject = function() {};
	// 公有和特权方法
	MyObject.prototype.publicMethod = function() {
		privateVariable++;
		return privateFunction();
	};
})();
var obj = new MyObject()
obj.publicMethod () //false

比如上面这种,我们使用一个自执行函数封装私有变量,创建一个构造函数表达式,我们没有用var定义,说明是一个全局的,在构造函数的原型上创建了一个publicMethod函数方法,这个函数就可以访问这个函数的词法作用域,就是一个闭包

我们也可以使用模块模式创建,返回一个单例对象
function singleton() {
	// 私有变量和私有函数
	let privateVariable = 10;
	function privateFunction() {
		return false;
	}
	// 特权/公有方法和属性
	return {
		publicProperty: true,
		publicMethod() {
			privateVariable++;
			return privateFunction();
		}
	};
}
var foo = singleton();
foo.publicMethod()  //false

singleton函数返回了一个对象,相当于一个模块实例,这个对象里面的publicMethod函数具有涵盖模块实例内部作用域的闭包,singleton函数也可以是自执行函数

小结

当函数可以记住并访问所在的词法作用域,即使函数是在当前词法作用域之外执行,这时就产生了闭包。闭包也是一个非常强大的工具,可以用多种形式来实现模块等模式。
闭包也会出现也写问题,比如将把HTML元素保存在某个闭包的作用域中,就相当于宣布该元素不能被销毁。或者是你把函数return出来后 他就给 window了所以一直存在内存中。会造成内存泄露。
function Handler() {
	let element = document.getElementById('element');
	let id = element.id;
	element.onclick = () => console.log(id);
	element = null;// 将element设置为null,解除对这个对象的引用,其引用计数也会减少,从而确保其内存可以在适当的时候被回收。
}

参考资料:
《你不知道的javascript》上卷
《javascript高级程序设计第四版》

;原文链接:https://blog.csdn.net/qq_42736311/article/details/115573668
本站部分内容转载于网络,版权归原作者所有,转载之目的在于传播更多优秀技术内容,如有侵权请联系QQ/微信:153890879删除,谢谢!
上一篇:拿捏链表(一)—— 移除链表元素 下一篇:没有了

推荐图文


随机推荐