详解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 相关文章推荐
javascript removeChild 使用注意事项
Apr 11 Javascript
用XMLDOM和ADODB.Stream实现base64编码解码实现代码
Nov 28 Javascript
VBS通过WMI监视注册表变动的代码
Oct 27 Javascript
js实现单行文本向上滚动效果实例代码
Nov 28 Javascript
使用JQUERY进行后台页面布局控制DIV实现左右式
Jan 07 Javascript
node.js中的Socket.IO使用实例
Nov 04 Javascript
简介JavaScript中用于处理正切的Math.tan()方法
Jun 15 Javascript
Jquery 效果使用详解
Nov 23 Javascript
layui2.0使用table+laypage实现真分页
Jul 27 Javascript
详解element-ui中表单验证的三种方式
Sep 18 Javascript
Element MessageBox弹框的具体使用
Jul 27 Javascript
JS前端可扩展的低代码UI框架Sunmao使用详解
Jul 23 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中Cannot send session cache limiter 的问题的方法
2007/04/27 PHP
PHP 防注入函数(格式化数据)
2011/08/08 PHP
PHP中运用jQuery的Ajax跨域调用实现代码
2012/02/21 PHP
有关PHP中MVC的开发经验分享
2012/05/17 PHP
PHP循环输出指定目录下的所有文件和文件夹路径例子(简单实用)
2014/05/10 PHP
win7 64位系统 配置php最新版开发环境(php+Apache+mysql)
2014/08/15 PHP
PHP编写登录验证码功能 附调用方法
2016/05/19 PHP
PHPCMS2008广告模板SQL注入漏洞修复
2016/10/11 PHP
ThinkPHP3.2框架自定义配置和加载用法示例
2018/06/14 PHP
PHP实现的分解质因数操作示例
2018/08/01 PHP
PHP生成随机码的思路与方法实例探索
2019/04/11 PHP
table insertRow、deleteRow定义和用法总结
2014/05/14 Javascript
JS 实现Base64编码与解码实例详解
2016/11/07 Javascript
基于Vue实例对象的数据选项
2017/08/09 Javascript
浅谈Vue-cli单文件组件引入less,sass,css样式的不同方法
2018/03/13 Javascript
Vue在页面右上角实现可悬浮/隐藏的系统菜单
2018/05/04 Javascript
webpack4 css打包压缩问题的解决
2018/05/18 Javascript
vue element table 表格请求后台排序的方法
2018/09/28 Javascript
react 应用多入口配置及实践总结
2018/10/17 Javascript
JS使用iView的Dropdown实现一个右键菜单
2019/05/06 Javascript
js图片无缝滚动插件使用详解
2020/05/26 Javascript
[03:41]DOTA2上海特锦赛小组赛第三日recap精彩回顾
2016/02/28 DOTA
详细解析Python当中的数据类型和变量
2015/04/25 Python
Python基于sftp及rsa密匙实现远程拷贝文件的方法
2016/09/21 Python
Python实现的基数排序算法原理与用法实例分析
2017/11/23 Python
PyQt5每天必学之拖放事件
2020/08/27 Python
nohup后台启动Python脚本,log不刷新的解决方法
2019/01/14 Python
Python根据成绩分析系统浅析
2019/02/11 Python
Python建造者模式案例运行原理解析
2020/06/29 Python
基于Python的图像阈值化分割(迭代法)
2020/11/20 Python
英国在线购买马术服装:EQUUS
2019/07/12 全球购物
《在山的那边》教学反思
2014/02/23 职场文书
个人校本研修方案
2014/05/26 职场文书
大学生党员学习焦裕禄精神思想汇报
2014/09/10 职场文书
南京大屠杀观后感
2015/06/02 职场文书
学生会2016感恩节活动小结
2016/04/01 职场文书