js函数高级:06、详解闭包(引入闭包、理解闭包、常见闭包、闭包作用、闭包生命周期、闭包应用、闭包缺点及解决方案)及相关面试题

javascript 的引入闭包、理解闭包、常见闭包、闭包作用、闭包生命周期、闭包应用、闭包缺点及解决方案及相关面试题解:

Ⅰ、闭包的引入:

其一、代码为:

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>01_引入</title>
</head>
<body>
	<button>测试1</button>
	<button>测试2</button>
	<button>测试3</button>
	<!-- 
	需求:点击某个按钮,提示"点击的是第 n 个按钮"
	-->
	<script type="text/javascript">
	var btns = document.getElementsByTagName('button')

	// 遍历加监听
	
	/* // for(var i = 0; i < btns.length; i++) {
	for(var i = 0, length = btns.length; i < length; i++) {// 注意:btns 是一个伪数组,且伪数组的 length 属性每次都要统计,需要计算一遍最终才能得到 length 结果,需要消耗多次的计算;
																				// 规避方案:length = btns.length 就可以,因为其并不像数组一样,里面有固定的 length 属性值,且不需要多次计算;
		var btn = btns[i]
		btn.onclick = function() {
			// alert('第' + (i+1) + '个')// 第4个,说明:函数执行的时候循环已经结束了(即:点击后一直输出为 4);
			console.log('第' + (i+1) + '个')// 第4个,说明:函数执行的时候循环已经结束了(即:点击后一直输出为 4);
		}
	}
	console.log(i)// 3,表示可以访问 i,因为 i 是全局变量(即:一直输出为 3);	
	 */



	
	/* for(var i = 0, length = btns.length; i < length; i++) {
		var btn = btns[i]
		// 将 btn 所对应的下标保存在 btn 上
		btn.index = i
		btn.onclick = function() {
			// alert('第' + (this.index+1) + '个')// 此时就点击对应的 button 值时,就显示对应 button 的 index 值(即:可以一一对应);
			console.log('第' + (this.index+1) + '个')// 此时就点击对应的 button 值时,就显示对应 button 的 index 值(即:可以一一对应);
		}
	} */
	




	// 利用闭包
	for(var i = 0, length = btns.length; i < length; i++) {// 此时的 i 是全局的;
		(function(i) {// 此时的 i 是局部的;
			var btn = btns[i]// 此时的 i 是局部的;
			btn.onclick = function() {
				// alert('第' + (i+1) + '个')// 此时就点击对应的 button 值时,就显示对应的 i 值;
				console.log('第' + (i+1) + '个')// 此时就点击对应的 button 值时,就显示对应的 i 值;
			}
		})(i)// 此时的 i 是全局的;
	}
	</script>
</body>
</html>

其二、结果为:

// 一进入页面后的控制台:

// 进入页面依次点击测试1、测试2、测试3后的控制台:

Ⅱ、闭包的理解:

其一、代码为:

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>02_理解闭包</title>
</head>
<body>
	<!--  
	1、如何产生闭包?
		* 当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时,就产生了闭包;

	2、闭包到底是什么?
		* 使用 chrome 调试查看;
		* 理解一:闭包是嵌套的内部函数(绝大部分人)
		* 理解二:包含被引用变量(函数)的对象(极少数人)
		* 注意:闭包存在于嵌套的内部函数中;

	3、产生闭包的条件?
		* 函数嵌套
		* 内部函数引用了外部函数的数据(变量/函数)
		*(*) 执行外部函数

	-->
	<script type="text/javascript">

	function fn1() {
		let a = 2
		let b = 'abc'
		function fn2() {// 执行函数定义就会产生闭包(不用调用内部函数,但由于语法(2025.08.11)等问题,现在已经只能调用才能看见闭包现象);
			console.log(a)
		}
		// return fn2// 应该是 ES6 或 chrome 的版本提升,必须要加上 return fn2 语句,才能在 fn1 函数中查看到 fn2 的函数提升,才能看到闭包现象(此时和调用时一个意思);
		// fn2() // 此时调用嵌套的内部函数可以看到闭包现象,也能看到嵌套的内部函数调用外部函数属性的情况;

		var fn3 = function() {// 此时没有执行函数定义,而是将 fn3 的变量提升,就不会产生闭包(只有调用才会看见闭包现象)
			console.log(a)
		}

		// return fn3
	}
	fn1()
	</script>
</body>
</html>

其二、结果为:

// 一进入页面后的控制台:

Ⅲ、常见的闭包:

其一、代码为:

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>03_常见的闭包</title>
</head>
<body>
	<!--
	1、将函数作为另一个函数的返回值;

	2、将函数作为实参传递给另一个函数调用;

	-->
	<script type="text/javascript">

  // 1、将函数作为另一个函数的返回值;
	function fn1() {
		var a = 2
		function fn2() {
			a++
			console.log(a)
		}
		return fn2
	}

	var f = fn1()// 整个过程中只产生了一个内部函数对象(即:只产生了一个闭包),f() 只是执行函数而非创建函数,而闭包对象是在创建函数的过程中产生的;
							 // 闭包对象产生个数的判断标准:就看外部函数执行的次数,而与内部函数执行的次数没有关系;
	f()// 3
	f()// 4,在反复执行内部函数的过程中,闭包里面的数据为什么没有消失? 答:因为内部函数还没执行完,闭包里的数据还在被调用,还没有完全释放内存,因此闭包里的数据未消失;
		




	// 2、将函数作为实参传递给另一个函数调用;
	function showDelay(msg, time) {// 存在闭包的原因:有内部和外部函数,内部函数使用了外部函数的变量(即:msg);
		setTimeout(() => {
			// alert(msg)
			console.log(msg)
		}, time);
	}
	showDelay('atHome',2000)// 2s 后出现弹窗,且显示为:atHome

	</script>
</body>
</html>

其二、结果为:

// 一进入页面后的控制台:

Ⅳ、闭包的作用:

其一、代码为:

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>04_闭包的作用</title>
</head>
<body>
	<!--
	1、使用函数内部的变量在函数执行完后,仍然存活在内存中(延长了局部变量的生命周期)
	2、让函数外部可以操作(读写)到函数内部的数据(变量/函数)
	
	问题:
		1、函数执行完后,函数内部声明的局部变量是否还存在?  答:外部函数执行完后,函数内部声明的局部变量依旧存在,仍旧存在于闭包里面(即:一般是不存在,存在于闭包中的变量才可能存在);
		// 在执行代码的过程中,f 函数存在,那么 f 指向的就是 fn3 的函数对象(注意:fn3 会被释放,释放的语句是 var f = fn1() 将地址赋值给 f 后 fn3 就被释放了),而该对象又关联着闭包(即:a 变量),
		// 因此外部函数执行完后,函数内部声明的局部 a 变量依旧存在,仍旧存在于闭包里面;

		2、在函数外部能直接访问函数内部的局部变量吗?  答:不能,但我们可以通过闭包让外部操作它(即:操作函数内部的局部变量);

	-->
	<script type="text/javascript">

  function fn1() {
		var a = 2
		function fn2() {
			a++
			console.log(a)
			// return a
		}
		function fn3() {
			a--
			console.log(a)
		}
		return fn3
	}

	// var f = fn1() // 此时是 return fn2
	// f()// 3		
	// f()// 4		

	var f = fn1() // 此时是 return fn3
	f()// 1		
	f()// 0		

	</script>
</body>
</html>

其二、结果为:

// 一进入页面后的控制台:

Ⅴ、闭包的生命周期:

其一、代码为:

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>05_闭包的生命周期</title>
</head>
<body>
	<!--
	1、产生:在嵌套内部函数定义执行完时就产生了(注意:不是在调用)

	2、死亡:在嵌套的内部函数成为垃圾对象时

	-->
	<script type="text/javascript">

	function fn1() {
		// 此时闭包就已经产生了(原因:函数提升,内部函数对象已经创建了)
		var a = 2
		function fn2() {
			a++
			console.log(a)
		}

		// var fn2 = function() { // 若以该种方式定义函数,那么产生闭包的时机是:28 行运行结束,因为:是变量提升,而不是函数提升(即:函数定义执行);
		// 	a++
		// 	console.log(a)
		// }

		return fn2
	}

	var f = fn1()
	f() // 3
	f() // 4

	f = null// 闭包死亡(原因:包含闭包的函数对象成为垃圾对象)
		
	</script>
</body>
</html>

其二、结果为:

// 一进入页面后的控制台:

Ⅵ、闭包的应用:

其一、myModule.js 的代码为:

javascript 复制代码
function myModule() {
  // 私有数据
  var msg = 'my home'

  // 操作数据的函数
  function doSomething() {
    console.log('doSomething() ' + msg.toUpperCase())
  }
  function doOtherthing() {
    console.log('doOtherthing() ' + msg.toLowerCase())
  }

  // 向外暴露对象(该对象包含了:给外部使用的方法)
  // return doSomething// 只向外暴露一个包含 n 个方法的对象或函数
  return {
    doSomething: doSomething,
    doOtherthing: doOtherthing
  }
}

其二、自定义JS模块1的代码为:

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>06_闭包的应用_自定义JS模块1</title>
</head>
<body>
	<!--
	闭包的应用2:定义 JS 模块
		* 具有特定功能的 js 文件;
		* 将所有的数据和功能都封装在一个函数内部(私有的);
		* 只向外暴露一个包含 n 个方法的对象或函数;
		* 模块的使用者,只需要通过模块暴露的对象调用方法来实现对应的功能;

	-->
	<script type="text/javascript" src="myModule.js"></script>
	<script type="text/javascript">
		// var fn = myModule()
		// fn()
		// fn()

		var module = myModule()
		module.doSomething()// doSomething() MY HOME
		module.doOtherthing()// doOtherthing() my home
	</script>
</body>
</html>

其三、结果为:

// 一进入页面后的控制台:

其四、myModule2.js 的代码为:

javascript 复制代码
// // 匿名函数自调用;
// (function () {
//   // 私有数据
//   var msg = 'my home'

//   // 操作数据的函数
//   function doSomething() {
//     console.log('doSomething() ' + msg.toUpperCase())
//   }
//   function doOtherthing() {
//     console.log('doOtherthing() ' + msg.toLowerCase())
//   }

//   // 向外暴露对象(该对象包含了:给外部使用的方法)
//   // 匿名函数自调用,将待暴露的东西添加为 window 的属性;
//   window.myModule2 = {// 有闭包
//     doSomething: doSomething,
//     doOtherthing: doOtherthing
//   }
// })()



// 匿名函数自调用;
(function (window) {
  // 私有数据
  var msg = 'my home'

  // 操作数据的函数
  function doSomething() {
    console.log('doSomething() ' + msg.toUpperCase())
  }
  function doOtherthing() {
    console.log('doOtherthing() ' + msg.toLowerCase())
  }

  // 向外暴露对象(该对象包含了:给外部使用的方法)
  // 匿名函数自调用,将待暴露的东西添加为 window 的属性;
  window.myModule2 = {// 有闭包
    doSomething: doSomething,
    doOtherthing: doOtherthing
  }
})(window)// 添加 window 形参的好处:代码压缩时,会把一些局部变量或函数变成 a,b,c,d 等单个字母的字符,因此全加上 window 时,可以将该值(即:window)全局修改为 w(即:便于代码压缩);

其五、自定义JS模块2的代码为:

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>07_闭包的应用_自定义JS模块2</title>
</head>
<body>
	<!--
	闭包的应用2:定义 JS 模块
		* 具有特定功能的 js 文件;
		* 将所有的数据和功能都封装在一个函数内部(私有的);
		* 只向外暴露一个包含 n 个方法的对象或函数;
		* 模块的使用者,只需要通过模块暴露的对象调用方法来实现对应的功能;

	-->
	<script type="text/javascript" src="myModule2.js"></script>
	<script type="text/javascript">
	
		myModule2.doSomething()// doSomething() MY HOME
		myModule2.doOtherthing()// doOtherthing() my home
		
	</script>
</body>
</html>

其六、结果为:

Ⅶ、闭包的缺点及解决:

其一、代码为:

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>08_闭包的缺点及解决</title>
</head>
<body>
	<!--  
	1、缺点:
		* 函数执行完成后,函数内的局部变量没有释放,占用内存时间会变长
		* 容易造成内存泄漏(内存泄漏:内存被白白占用着,却用不上)

	2、解决:
		* 能不用闭包就不用(但:很难做到,因此只有及时释放才是解决闭包的方案)
		* 及时释放

	-->
	<script type="text/javascript">
		function fn1() {
			var arr = new Array(100000)
			function fn2() {
				console.log(arr.length)
			}
			return fn2
		}
		var f = fn1()
		f()

		f = null// 此时就能解决因闭包而导致的局部变量未释放的问题;
						// 让内部函数成为垃圾对象 ==> 回收闭包对象(即:释放局部变量);
		
	</script>
</body>
</html>

其二、结果为:

// 一进入页面后的控制台:

Ⅷ、面试题:

其一、面试题1代码:

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>09_面试题1</title>
</head>
<body>
	<script type="text/javascript">

	// 代码片段1:没有闭包
	var name = "The Window"
	var object = {
		name: "My Object",
		getNameFunc: function() {
			return function() {
				return this.name
			}
		}
	}
	// alert(object.getNameFunc()())// "The Window"
	console.log(object.getNameFunc()())// "The Window"
	// 原因:object.getNameFunc() 此时 getNameFunc() 内部的 this 指向为 object,而 object.getNameFunc()() 的 this 指向是 window,
	// 因此 this.name 代表的就是 window 下的 name 为 "The Window";




	// 代码片段2:存在闭包
	var name2 = "The Window"
	var object2 = {
		name2: "My Object",
		getNameFunc: function() {
			var that = this
			return function() {
				return that.name2
			}
		}
	}
	// alert(object2.getNameFunc()())// "My Object"
	console.log(object2.getNameFunc()())// "My Object"
	// 原因:object2.getNameFunc() 此时 getNameFunc() 内部的 this 指向为 object2,因此 that 保存的就是 object2,而 object2.getNameFunc()() 的指向是 window,
	// 但 that 指向的依旧是 object2,因此 that.name2 代表的就是 object2 下的 name2 为 "My Object";
		
	</script>
</body>
</html>

其二、结果为:

// 一进入页面后的控制台:

其三、面试题2代码:

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>10_面试题2</title>
</head>
<body>
	<script type="text/javascript">

		function fun(n,o) {
			console.log(o)
			return {
				fun: function(m) {
					return fun(m,n)// 存在闭包:此时回调函数的参数 n 是调用外部函数的变量值;
				}
			}
		}
		var a = fun(0)// undefined(此时是调用外部的 fun 函数,且产生了新的闭包,因为有返回值)
		a.fun(1)// 0(此时是调用内部的 fun 函数,且未产生新的闭包(本质也产生了,但没有接收就又消失了),使用的是 a 闭包里的值)
		a.fun(2)// 0(此时是调用内部的 fun 函数,且未产生新的闭包(本质也产生了,但没有接收就又消失了),使用的是 a 闭包里的值)
		a.fun(3)// 0(此时是调用内部的 fun 函数,且未产生新的闭包(本质也产生了,但没有接收就又消失了),使用的是 a 闭包里的值)

		var b = fun(0).fun(1).fun(2).fun(3)// undefined, 0, 1, 2

		var c = fun(0).fun(1)// undefined, 0
		c.fun(2)// 1
		c.fun(3)// 1
		
	</script>
</body>
</html>

其四、结果为:

// 一进入页面后的控制台:

Ⅸ、小结:

其一、哪里有不对或不合适的地方,还请大佬们多多指点和交流!
其二、若有转发或引用本文章内容,请注明本博客地址(直接点击下面 url 跳转) https://blog.csdn.net/weixin_43405300,创作不易,且行且珍惜!
其三、有兴趣的话,可以多多关注这个专栏(Vue(Vue2+Vue3)面试必备专栏)(直接点击下面 url 跳转):https://blog.csdn.net/weixin_43405300/category_11525646.html?spm=1001.2014.3001.5482
其四、再有兴趣的话,也可以多多关注这个专栏(Java)(直接点击下面 url 跳转):https://blog.csdn.net/weixin_43405300/category_12654744.html?spm=1001.2014.3001.5482

相关推荐
崔庆才丨静觅5 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60616 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了6 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅6 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅6 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅7 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment7 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅7 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊7 小时前
jwt介绍
前端
爱敲代码的小鱼7 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax