详解JavaScript作用域 闭包


Posted in Javascript onJuly 29, 2020

JavaScript闭包,是JS开发工程师必须深入了解的知识。3月份自己曾撰写博客《JavaScript闭包》,博客中只是简单阐述了闭包的工作过程和列举了几个示例,并没有去刨根问底,将其弄明白!

现在随着对JavaScript更深入的了解,也刚读完《你不知道的JavaScript(上卷)》这本书,所以乘机整理下,从底层和原理上去刨一下。

JavaScript并不具有动态作用域,它只有词法作用域。词法作用域是在写代码或者说定义时确定的,而动态作用域是在运行时确定的。了解闭包前,首先我们得知道什么是词法作用域(作用域是由书写代码时函数声明的位置来决定的)。

一、何为闭包

示例1:

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

在foo()执行后,通常认为垃圾回收机制会将foo()的整个内部作用域都被销毁;而闭包可以阻止这样事情发生,让其内部作用域依然存在。因为bar()处于foo()内部,它拥有涵盖foo()作用域的闭包,使得该作用域能够一直存活,以供bar()在之后任何时间进行引用。

bar()依然持有对该作用域的引用,而这个引用就叫作闭包。

简言之:当函数可以记住并访问所在的词法作用域,即使函数是在当前词法作用域之外执行,这时就产生了闭包。

示例2:

无论使用何种方式对函数类型的值进行传递,当函数在别处被调用时都可以观察到闭包。

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

function bar(fn){
	fn();	// 这就是闭包
}

示例3:

将一个内部函数(timer)传递给setTimeout。timer具有涵盖wait()作用域的闭包,保有对变量message的引用。

wait()执行1000毫秒后,它的作用域并不会消失,timer依然保有wait()作用域的闭包。

function wait(message){
	setTimeout( function timer(){
		console.log(message);
	},1000);
}
wait("Hello,ligang");

示例4:

下述activator()具有涵盖setupBot()作用域的闭包!

function setupBot(name, selector){
	$(selector).click(function activator(){
		console.log("Activating: "+ name);
	});
}
setupBot("Closure Bot 1", "#bot_1");
setupBot("Closure Bot 2", "#bot_2");

二、循环和闭包

for(var i=1; i<=5; i++){
	setTimeout(function timer(){
		console.log(i);
	}, i*1000);
}
// 期望:每秒一次的频率输出1~5
// 结果:每秒一次的频率输出五次6

先解释一下:“i*1000”,5个定时分别在1s、2s、3s、4s、5s后执行,并不是1s、3s、6s、10s、15s。也就是频率为1s,不是每次间隔增加1s。如果去掉i写成“1000”,会在for执行完1s后直接输出五次6。

回调函数在循环结束后才被执行,因此输出的是循环终止条件是i值。事实上,当定时器运行时即使每个迭代中执行的是setTimeout(..., 0),所有的回调函数依然是在循环结束后才被执行。

根据作用域的工作原理,尽管五个函数是在各个迭代中分别定义的,但是它们都被封闭在一个共享的全局作用域中,因此实际上只有一个i。

解决方案1:

for(var i=0; i<=5; i++){
	(function(j){
		setTimeout(function timer(){
			console.log(j);
		}, j*1000 );
	})(i);
}
// 结果:每秒一次的频率输出1~5

每个迭代都生成一个新的作用域,使得延迟函数的回调可以将新的作用封闭在每个迭代内部,每个迭代中都会含有一个具有正确值的变量供我们访问。

解决方案2(ES6):

for(var i=0; i<=5; i++){
	let j = i;
	setTimeout(function timer(){
		console.log(j);
	}, j*1000 );
}
// 结果:每秒一次的频率输出1~5
for(let i=0; i<=5; i++){
	setTimeout(function timer(){
		console.log(i);
	}, i*1000 );
}
// 结果:每秒一次的频率输出五次6

三、模块

模块需要具备两个必要条件:

(1)必须有外部的封闭函数,该函数必须至少被调用一次(每次调用都会创建一个新的模块实例)。

(2)封闭函数必须返回至少一个内部函数,这样内部函数才能在私有作用域中形成闭包,并且可以访问或者修改私有的状态。

典型的模块化:

function CoolMoudle(){
	var something = "cool";
	var doSomething = function(){
		console.log(something);
	}
	return{
		doSomething: doSomething
	};
}
var foo = CoolMoudle();	//如果不执行外部函数CoolMoudle(),内部作用域和闭包都无法创建
foo.doSomething();	//cool

单例模式:

var foo = (function CoolModule(id){
	function change(){
		// 修改公共API
		publicAPI.identify = identify2;
	}
	function identify1(){
		console.log(id);
	}
	function identify2(){
		console.log(id.toUpperCase());
	}
	var publicAPI = {
		change: change,
		identify: identify1
	};
	return publicAPI;
})("foo module");
foo.identify();	//foo module
foo.change();
foo.identify(); //FOO MODULE

以上就是详解JavaScript作用域 闭包的详细内容,更多关于JavaScript作用域 闭包的资料请关注三水点靠木其它相关文章!

Javascript 相关文章推荐
模仿jQuery each函数的链式调用
Jul 22 Javascript
javascript:void(0)点击登录没反应怎么解决
Nov 13 Javascript
学习AngularJs:Directive指令用法(完整版)
Apr 26 Javascript
浅谈jquery中的each方法$.each、this.each、$.fn.each
Jun 23 Javascript
Bootstrap 3浏览器兼容性问题及解决方案
Apr 11 Javascript
JS实现百度搜索接口及链接功能实例代码
Feb 02 Javascript
vue-cli webpack2项目打包优化分享
Feb 07 Javascript
vue 监听某个div垂直滚动条下拉到底部的方法
Sep 15 Javascript
JS温故而知新之变量提升和时间死区
Jan 27 Javascript
一文快速了解JQuery中的AJAX
May 31 jQuery
js之切换全屏和退出全屏实现代码实例
Sep 09 Javascript
layui表格 返回的数据状态异常的解决方法
Sep 10 Javascript
Angular+ionic实现折叠展开效果的示例代码
Jul 29 #Javascript
Vue 监听元素前后变化值实例
Jul 29 #Javascript
使用eslint和githooks统一前端风格的技巧
Jul 29 #Javascript
vue-cli或vue项目利用HBuilder打包成移动端app操作
Jul 29 #Javascript
小程序实现列表展开收起效果
Jul 29 #Javascript
jquery实现简单自动轮播图效果
Jul 29 #jQuery
解决vue-photo-preview 异步图片放大失效的问题
Jul 29 #Javascript
You might like
一步一步学习PHP(6) 面向对象
2010/02/16 PHP
php返回相对时间(如:20分钟前,3天前)的方法
2015/04/14 PHP
php基于mcrypt_encrypt和mcrypt_decrypt实现字符串加密解密的方法
2016/07/12 PHP
PHP 多任务秒级定时器的实现方法
2018/05/13 PHP
利用javascript实现一些常用软件的下载导航
2009/08/03 Javascript
js判断上传文件的类型和大小示例代码
2013/10/18 Javascript
详细解读JavaScript的跨浏览器事件处理
2015/08/12 Javascript
js的form表单提交url传参数(包含+等特殊字符)的两种解决方法
2016/05/25 Javascript
Node.js中使用jQuery的做法
2016/08/17 Javascript
EasyUI创建对话框的两种方式
2016/08/23 Javascript
Vue实现类似Spring官网图片滑动效果方法
2019/03/01 Javascript
JavaScript实现随机点名器
2020/03/25 Javascript
JS一次前端面试经历记录
2020/03/19 Javascript
javascript前端和后台进行数据交互方法示例
2020/08/07 Javascript
解决vue单页面应用进入页面加载所有 js 的问题
2020/08/12 Javascript
[29:23]2014 DOTA2国际邀请赛中国区预选赛 LGD-GAMING VS CIS 第一场1
2014/05/23 DOTA
[40:12]Liquid vs Chaos 2019国际邀请赛小组赛 BO2 第二场 8.15
2019/08/16 DOTA
python实现根据窗口标题调用窗口的方法
2015/03/13 Python
python操作列表的函数使用代码详解
2017/12/28 Python
基于Django ORM、一对一、一对多、多对多的全面讲解
2019/07/26 Python
在python3中实现更新界面
2020/02/21 Python
python实现图片横向和纵向拼接
2020/03/05 Python
python如何建立全零数组
2020/07/19 Python
Flask处理Web表单的实现方法
2021/01/31 Python
La Redoute英国官网:法国时尚品牌
2017/04/27 全球购物
英国音乐设备和乐器商店:Gear4music
2017/10/16 全球购物
Crabtree & Evelyn欧盟:豪华洗浴、身体和护发
2021/03/09 全球购物
服装设计专业毕业生推荐信
2013/11/09 职场文书
电子信息工程自荐信
2014/05/26 职场文书
比赛口号大全
2014/06/10 职场文书
人力资源管理专业自荐信
2014/06/24 职场文书
2016党员学习心得体会范文
2016/01/23 职场文书
2016年青少年禁毒宣传教育活动总结(学校)
2016/04/05 职场文书
windows11怎么查看自己安装的版本号? win11版本号的查看方法
2021/11/21 数码科技
golang用type-switch判断interface的实际存储类型
2022/04/14 Golang
Python实现数据的序列化操作详解
2022/07/07 Python