看了这一篇你还不理解什么是闭包——我直播吃翔!

看了这一篇你还不理解什么是闭包——我直播吃翔!

一. 前言

许多刚刚接触JavaScript的小伙伴可能对闭包一脸懵逼,难于理解。对于那些有一点JavaScript使用经验但未真正理解闭包的人来说,理解闭包会使你功力大增。恰巧这篇文章正是为你准备的。
有些小伙伴对闭包有了一定理解,但是并不清楚在JavaScript具体有哪些应用,现在我告诉你。在JavaScript中闭包无处不在,只是你还没有发现它。闭包是基于词法作用域书写代码时产生的自然结果,你不需要为了利用它而有意识的创建闭包,因为闭包已经在你的代码中无处不在。
相信我,看完这篇文章,你一定会恍然大悟,原来我的代码中已经到处都是闭包了。

二.准备知识

为了理解闭包,你必须对词法作用域作用域链有一定的理解。

三.闭包

闲话少说,直接上干货
对于闭包直接了当的定义:

当函数可以记住并访问所在的词法作用域时,就产生了闭包 (你不知道的JavaScript)
闭包是指有权访问另一个函数作用域中的变量的函数(JavaScript高级程序设计)

直接上代码

function foo() {
	var a = 2;
	return function fun1() {
		console.log(a)
	}
}
var fun2 = foo()
fun2()  // 2

在上面的例子中,fun1能够访问foo的内部作用域,我们把fun1作为一个值返回。在foo()执行后,把foo()的返回值 fun1 赋值给fun2并调用fun2。打印出了结果2.
此时,我们可以说fun1记住并访问了所在的词法作用域 或者说 fun2访问了另一个函数作用域中的变量(fun2在全局作用域中声明,访问了foo的内部作用域)
由于引擎有自动的垃圾回收机制,在foo()执行后(不再使用),通常foo的整个内部作用域会被销毁,对内存进行回收。闭包的神奇之处正是可以阻止这件事情的发生,因为fun1依然持有对该作用域的引用,这个引用就叫做闭包。
无论使用何种方式对函数类型的值进行传递,当函数在别处调用时,都可以看到闭包。
直接传递

function foo() {
	var a = 2;
	function baz() {
		console.log(a);
	}
	bar(baz); //对函数类型进行值得传递
}
function bar(fn) {
	fn(); // 闭包产生了
}

或者间接的传递

var fn;
function foo(){
	var a = 2;
	function baz() {
		console.log(a);
	}
	fn = baz; //对函数类型进行值得传递
}
function bar(fn) {
	fn(); //闭包产生了
}
foo()
bar()

无论通过什么手段将内部函数传递到所在的词法作用域之外,它都会保持对定义时作用域的引用,这个函数无论在何处执行,都产生了闭包。

现在你理解闭包了吗?思考一下你的代码中产生了哪些闭包?

四. 无处不在的闭包

1.定时器

function wait(message) {
	setTimeout(function timer() {
		console.log(message)
	}, 1000)
}
wait('hello 闭包')

将函数timer传递给setTimeout(), 内部函数timer 保持了对wait的作用域的引用,1000毫秒后打印出‘hello 闭包’。

2. 或者你使用过jQuery

function doSomeing(selector,doWhat) {
	$(selector).onClick(function(){
		console.log(doWhat)
	})
}
doSomeing('#dom1','dowhat')
doSomeing('#dom2','dowhat2')

jquery的click事件处理函数保持了对doSomeing的作用域的引用。当click事件发生时,闭包也产生了。

3.ajax

function foo(someData){
	$.ajax({
		data:data,
		url:url,
		success:function(result){
			console.log(someData)
		}
	})
}
foo('我也是闭包')

ajax的success回调函数保持了对foo的作用域的引用,这也是闭包。

现在你理解了吗?在定时器,事件监听器,Ajax请求,跨窗口通信,Web Workers 或者其他任何的异步(或同步)任务中,只要使用了回调函数,实际上都是在应用闭包。

还有一个IIFE模式(立即执行函数)需要单独说一下

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

虽然它能够正确的执行,但是严格意义上来说,它并不是一个闭包,因为它不是在本身的词法作用域之外执行的。我认为,因为它是在原始定义时的词法作用域中立即执行,也是保存了对原始定义时的词法作用域的引用。它确确实实产生了闭包的效果。

五. 循环和闭包

思考下面的代码

for (var i=1;i<=5;i++){
	setTimeout(function timer() {
		console.log(i)
	},i*1000)
}

正常情况下,我们对这段代码的预期是每隔一秒输出一个数字,1-5。但是实际上并不会这样,这段代码会每间隔一秒输出一个数字6。这是为什么?

首先6从哪里来,当timer执行时,for循环早已结束,终止条件是i=6。
有些小伙伴可能会不明白了,timer不是创建了闭包,保持了对i的引用吗?
没错。for循环了5次,创建了5个闭包,但是都是保持了对同一个i的引用(i是全局变量)。所以当timer执行时,i = 6,输出6,没毛病。
那么如何达到我们想要的效果,我们需要更多的闭包。循环过程中每个迭代都需要一个闭包

for (var i=1;i<=5;i++){
	(function(j){
		setTimeout(function timer() {
			console.log(j)
		},j*1000)
	})(i)
}

立即执行函数创建了一个新的作用域,使得延迟函数的的回调可以将新的作用域封闭在每个迭代内部。问题解决。

现在你理解闭包了吗? 大声的告诉我。你的代码中还有其他闭包的应用吗?现在停下你手中的事情,查看你最近的写的代码,看看有没有闭包的应用吧☺

如果这篇文章有帮到你,点个赞再走呗!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章