聊聊JavaScript闭包

看来很多文章,想找一个简单、清晰又不晦涩难懂的闭包定义,可惜没有找到。算啦,暂时先不给出闭包的定义了,先来看一个例子。

function parentFun(x){
	var num = 5;
	function childFun(y){
		console.log(x + y + (++num));
	}
	childFun(5);
}
parentFun(3);//14

parentFun函数里面嵌套childFun函数,因此内部的childFun函数可以访问到外部的parentFun函数里的变量。上面的parentFun(3)不管你运行多少次,得到的结果都是14。但这不是闭包,但你return内部函数childFun时,就是一个闭包了,如下代码

function parentFun(x){
	var num = 5;
	return function (y){
		console.log(x + y + (++num));
	}
};
var one = parentFun(3);//one 现在是一个闭包
one(5);//14
one(5);//15
one(5);//16

这段代码内部函数闭包了外部parentFun函数的变量直到内部函数运行结束,虽然one不能直接处于parentFun的内部作用于,但one还是能访问x和num。由于num变量存在于one函数闭包的内部,所以每次调用one函数,num变量还是会自加1.

注意:返回的函数并没有立即执行,直到调用了one()函数才执行

闭包的坑点——对变量的引用

function count(){
	var arr = [];
	for(var i = 1; i < 4; i++){
		arr.push(function(){
			return i * i;
		});
	}
	return arr;
}
var results = count();
var f1 = results[0];
var f2 = results[1];
var f3 = results[2];
f1();//16
f2();//16
f3();//16

你可能认为结果应该是1、4、9,但实际结果却都是16。原因就在于返回函数引用了变量i,变量i并没有理解执行。当等到三个函数都返回时,变量i已经变为4了,所以最终结果都为16。

牢记:返回闭包时,返回函数不要引用任何循环变量,或者后续会发生变化的变量。

如果一定要引用循环变量那该怎么办呢?

function count(){
	var arr = [];
	for (var i = 1; i < 4; i++){
		arr.push((function(n){
			return function(){
				return n * n;
			}
		})(i));
	}
	return arr;
}
var results = count();
var f1 = results[0];
var f2 = results[1];
var f3 = results[2];
f1();//1
f2();//4
f3();//9

这里用了一个匿名函数并立即执行的语法,匿名函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变。

ECMAScript 2015引入 let 关键字之后,可以很容易的解决闭包变量引用问题。

function count(){
	let arr = [];
	for(let i = 1; i < 4; i++){
		arr.push(function(){
			return i * i;
		});
	}
	return arr;
}
let results = count();
let f1 = results[0];
let f2 = results[1];
let f3 = results[3];
f1();//1
f2();//4
f3();//9

闭包的应用点——封装私有变量

'use strict';
function counter(num){
	var x = num || 0;
	return {
		addOne: function(){
			x += 1;
			return x;
		}
	}
};
var one = counter();
one.addOne();//1
one.addOne();//2
one.addOne();//3
var one = counter(5);
one.addOne();//6
one.addOne();//7
one.addOne();//8

返回对象中实现了一个闭包,该闭包携带了局部变量x,并且外部代码无法访问到该变量x。也可以说闭包就是携带了状态的函数,并且它的状态可以对外界完全隐藏起来。

重新理解一下闭包
闭包就是能够读取其他函数内部变量的函数,内部函数没有被释放,整条作用域链上的局部变量都将得到保留。
由于在javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成‘定义在一个函数内部的函数’。
所以在本质上,闭包就是将函数内部和函数外部连接的一座桥梁。

在看一个例子,使用Promise解决闭包问题

const tasks = [];
for(var i = 0; i < 5; i++){
	((j)=>{
		tasks.push(new Promise((resolve) => {
			setTimeout(()=>{
				console.log(j);
				resolve();//resolve是一定要的,否则代码不会按照预期运行
			}, 1000*j)
		}))
	})(i)
}
Promise.all(tasks).then(()=>{
	setTimeout(()=>{
		console.log(i);
	},1000);
});
//结果每隔一秒依次打印出0,1,2,3,4,5

总结一下:如果不是某些特定任务需要使用闭包,在其它函数中创建函数是不明智的,因为闭包在处理速度和内存消耗方面对脚本性能具有负面影响。

参考资料1
参考资料2

猜你喜欢

转载自blog.csdn.net/z1324402468/article/details/85954278