详解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关于select的相关操作说明
Jan 13 Javascript
JavaScript中函数声明优先于变量声明的实例分析
Mar 01 Javascript
JavaScript获取鼠标移动时的坐标(兼容IE8、chome谷歌、Firefox)
Sep 13 Javascript
javascript实现控制的多级下拉菜单
Jul 05 Javascript
有关Promises异步问题详解
Nov 13 Javascript
jquery——九宫格大转盘抽奖实例
Jan 16 Javascript
Angular 2父子组件数据传递之@Input和@Output详解 (上)
Jul 05 Javascript
jQuery中元素选择器(element)简单用法示例
May 14 jQuery
JS实现的input选择图片本地预览功能示例
Aug 29 Javascript
vue router 跳转后回到顶部的实例
Aug 31 Javascript
js图片无缝滚动插件使用详解
May 26 Javascript
js使用文档就绪函数动态改变页面内容示例【innerHTML、innerText】
Nov 07 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编程中字符串处理的5个技巧小结
2007/11/13 PHP
php防止网站被攻击的应急代码
2015/10/21 PHP
thinkphp中AJAX返回ajaxReturn()方法分析
2016/12/06 PHP
Zend Framework数据库操作技巧总结
2017/02/18 PHP
JavaScript 提升运行速度之循环篇 译文
2009/08/15 Javascript
jquery一句话全选/取消全选
2011/03/01 Javascript
JavaScript将相对地址转换为绝对地址示例代码
2013/07/19 Javascript
JavaScript Window浏览器对象模型方法与属性汇总
2015/04/20 Javascript
javascript表单控件实例讲解
2016/09/13 Javascript
JavaScript mixin实现多继承的方法详解
2017/03/30 Javascript
在使用JSON格式处理数据时应该注意的问题小结
2017/05/20 Javascript
详解最新vue-cli 2.9.1的webpack存在问题
2017/12/16 Javascript
详解在微信小程序的JS脚本中使用Promise来优化函数处理
2019/03/06 Javascript
小程序Request的另类用法详解
2019/08/09 Javascript
原生javascript制作的拼图游戏实现方法详解
2020/02/23 Javascript
Vue 电商后台管理项目阶段性总结(推荐)
2020/08/22 Javascript
vue中的循环对象属性和属性值用法
2020/09/04 Javascript
微信小程序实现购物车小功能
2020/12/30 Javascript
[56:12]LGD vs Optic Supermajor小组赛D组胜者组决赛 BO3 第一场 6.3
2018/06/04 DOTA
[01:02:04]EG vs Liquid 2019国际邀请赛淘汰赛 败者组 BO3 第一场 8.23
2019/09/05 DOTA
[03:07]完美世界DOTA2联赛PWL DAY10 决赛集锦
2020/11/11 DOTA
python实现12306抢票及自动邮件发送提醒付款功能
2018/03/08 Python
Python根据欧拉角求旋转矩阵的实例
2019/01/28 Python
详解pandas.DataFrame中删除包涵特定字符串所在的行
2019/04/04 Python
python 实现在tkinter中动态显示label图片的方法
2019/06/13 Python
python redis连接 有序集合去重的代码
2019/08/04 Python
python的reverse函数翻转结果为None的问题
2020/05/11 Python
Marriott国际:万豪国际酒店查询预订
2017/09/25 全球购物
Nike挪威官网:Nike.com (NO)
2018/11/26 全球购物
印度尼西亚手表和包包商店:Urban Icon
2019/12/12 全球购物
Booking.com亚太地区:Booking.com APAC
2020/02/07 全球购物
大学团日活动新闻稿
2014/09/10 职场文书
勤俭节约倡议书范文
2015/04/29 职场文书
大学生各类奖学金申请书
2019/06/24 职场文书
Linux中如何安装并部署Redis
2022/04/18 Servers
详解Mysq MVCC多版本的并发控制
2022/04/29 MySQL