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

相关推荐
深红1 小时前
玩转小程序AR-基础篇
前端·微信小程序·webvr
风止何安啊1 小时前
从 “牵线木偶” 到 “独立个体”:JS 拷贝的爱恨情仇(浅拷贝 VS 深拷贝)
前端·javascript·面试
漫天黄叶远飞1 小时前
地址与地基:在 JavaScript 的堆栈迷宫里,重新理解“复制”的哲学
前端·javascript·面试
杨啸_新房客1 小时前
如何优雅的设置公司的NPM源
前端·npm
ohyeah1 小时前
深入理解 JavaScript 中的继承与 instanceof 原理
前端·javascript
linhuai1 小时前
flutter如何实现有登陆权限管理
前端
crary,记忆1 小时前
React 之 useEffect
前端·javascript·学习·react.js
皮皮学姐分享-ppx2 小时前
中国绿色制造企业数据(绿色工厂|绿色供应链|绿色园区|绿色产品,2017-2023)
大数据·人工智能·经验分享·科技·区块链·制造
BD_Marathon2 小时前
【JavaWeb】HTML常见标签——标题段落和换行
前端·html