闭包详解一

在正式学习闭包以前,请各位同窗必定要确保本身对词法做用域已经很是的熟悉了,若是对词法做用域还不够熟悉的话,能够先看:前端

前言

如今去面试前端开发的岗位,若是你对面试官也是个前端,而且不是太水的话,你有很大的几率会被问到JavaScript中的闭包。由于这个闭包这个知识点真的很重要,还很是难掌握。面试

什么是闭包

什么是闭包,你可能会搜出不少答案....bash

《JavaScript高级程序设计》这样描述:闭包

闭包是指有权访问另外一个函数做用域中的变量的函数;函数

《JavaScript权威指南》这样描述:post

从技术的角度讲,全部的JavaScript函数都是闭包:它们都是对象,它们都关联到做用域链。学习

《你不知道的JavaScript》这样描述:ui

当函数能够记住并访问所在的词法做用域时,就产生了闭包,即便函数是在当前词法做用域以外执行。spa

我最认同的是《你不知道的JavaScript》中的描述,虽然前面的两种说法都没有错,但闭包应该是基于词法做用域书写代码时产生的天然结果,是一种现象!你也不用为了利用闭包而特地的建立,由于闭包的在你的代码中随处可见,只是你还不知道当时你写的那一段代码其实就产生了闭包。设计

讲解闭包

上面已经说到,当函数能够记住并访问所在的词法做用域时,就产生了闭包,即便函数是在当前词法做用域以外执行

看一段代码

function fn1() {
	var name = 'iceman';
	function fn2() {
		console.log(name);
	}
	fn2();
}
fn1();
复制代码

若是是根据《JavaScript高级程序设计》和《JavaScript权威指南》来讲,上面的代码已经产生闭包了。fn2访问到了fn1的变量,知足了条件“有权访问另外一个函数做用域中的变量的函数”,fn2自己是个函数,因此知足了条件“全部的JavaScript函数都是闭包”。

这的确是闭包,可是这种方式定义的闭包不太好观察。

再看一段代码:

function fn1() {
	var name = 'iceman';
	function fn2() {
		console.log(name);
	}
	return fn2;
}
var fn3 = fn1();
fn3();
复制代码

这样就清晰地展现了闭包:

  • fn2的词法做用域能访问fn1的做用域

  • 将fn2当作一个值返回

  • fn1执行后,将fn2的引用赋值给fn3

  • 执行fn3,输出了变量name

咱们知道经过引用的关系,fn3就是fn2函数自己。执行fn3能正常输出name,这不就是fn2能记住并访问它所在的词法做用域,并且fn2函数的运行仍是在当前词法做用域以外了。

正常来讲,当fn1函数执行完毕以后,其做用域是会被销毁的,而后垃圾回收器会释放那段内存空间。而闭包却很神奇的将fn1的做用域存活了下来,fn2依然持有该做用域的引用,这个引用就是闭包

总结:某个函数在定义时的词法做用域以外的地方被调用,闭包可使该函数极限访问定义时的词法做用域

注意:对函数值的传递能够经过其余的方式,并不必定值有返回该函数这一条路,好比能够用回调函数:

function fn1() {
	var name = 'iceman';
	function fn2() {
		console.log(name);
	}
	fn3(fn2);
}
function fn3(fn) {
	fn();
}
fn1();
复制代码

本例中,将内部函数fn2传递给fn3,当它在fn3中被运行时,它是能够访问到name变量的。

因此不管经过哪一种方式将内部的函数传递到所在的词法做用域之外,它都回持有对原始做用域的引用,不管在何处执行这个函数都会使用闭包。

再次解释闭包

以上的例子会让人以为有点学院派了,可是闭包毫不仅仅是一个无用的概念,你写过的代码当中确定有闭包的身影,好比相似以下的代码:

function waitSomeTime(msg, time) {
	setTimeout(function () {
		console.log(msg)
	}, time);
}
waitSomeTime('hello', 1000);
复制代码

定时器中有一个匿名函数,该匿名函数就有涵盖waitSomeTime函数做用域的闭包,所以当1秒以后,该匿名函数能输出msg。

另外一个很经典的例子就是for循环中使用定时器延迟打印的问题:

for (var i = 1; i <= 10; i++) {
	setTimeout(function () {
		console.log(i);
	}, 1000);
}
复制代码

在这段代码中,咱们对其的预期是输出1~10,但却输出10次11。这是由于setTimeout中的匿名函数执行的时候,for循环都已经结束了,for循环结束的条件是i大于10,因此固然是输出10次11咯。

究其缘由:i是声明在全局做用中的,定时器中的匿名函数也是执行在全局做用域中,那固然是每次都输出11了。

缘由知道了,解决起来就简单了,咱们可让i在每次迭代的时候,都产生一个私有的做用域,在这个私有的做用域中保存当前i的值。

for (var i = 1; i <= 10; i++) {
	(function () {
		var j = i;
		setTimeout(function () {
			console.log(j);
		}, 1000);
	})();
}
复制代码

这样就达到咱们的预期了呀,让咱们用一种比较优雅的写法改造一些,将每次迭代的i做为实参传递给自执行函数,自执行函数中用变量去接收:

for (var i = 1; i <= 10; i++) {
	(function (j) {
		setTimeout(function () {
			console.log(j);
		}, 1000);
	})(i);
}
复制代码

闭包的应用

闭包的应用比较典型是定义模块,咱们将操做函数暴露给外部,而细节隐藏在模块内部:

function module() {
	var arr = [];
	function add(val) {
		if (typeof val == 'number') {
			arr.push(val);
		}
	}
	function get(index) {
		if (index < arr.length) {
			return arr[index]
		} else {
			return null;
		}
	}
	return {
		add: add,
		get: get
	}
}
var mod1 = module();
mod1.add(1);
mod1.add(2);
mod1.add('xxx');
console.log(mod1.get(2));
复制代码

关于闭包还有不少要讲,这里先讲解比较基础的概念,接下来还会有更精彩的内容。

特别注意

能够关注个人公众号:icemanFE,接下来持续更新技术文章!

公众号.png
相关文章
相关标签/搜索