javascript 的引入闭包、理解闭包、常见闭包、闭包作用、闭包生命周期、闭包应用、闭包缺点及解决方案及相关面试题解:
- Ⅰ、闭包的引入:
- Ⅱ、闭包的理解:
- Ⅲ、常见的闭包:
- Ⅳ、闭包的作用:
- Ⅴ、闭包的生命周期:
- Ⅵ、闭包的应用:
-
- [其一、myModule.js 的代码为:](#其一、myModule.js 的代码为:)
- 其二、自定义JS模块1的代码为:
- 其三、结果为:
- [其四、myModule2.js 的代码为:](#其四、myModule2.js 的代码为:)
- 其五、自定义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>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