🎱 JavaScript 闭包
🌰 例子引入:循环遍历加监听:
初始:
使用闭包改进初始思路:
# 闭包的概念
如何产生闭包:当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时,就产生了闭包。
闭包的理解:(通过
devTools
查看closure
)- 闭包是嵌套的内部函数。(极大部分的人的理解)
- 包含被引用变量(函数)的对象。(少数人理解)
⚠️ 注意:闭包存在于嵌套的内部函数中。
产生闭包的条件:
- 函数嵌套。
- 内部函数引用了外部函数的数据。(执行外部函数定义就会产生闭包,不必调用内部函数)
🌰: 例子
function fn1 (){
var a = 2
function fn2 (){
console.log(a)
}
}
fn1()
2
3
4
5
6
7
常见的闭包:
🌰 例子 1️⃣:将函数作为另一个函数的返回值:
function fn1(){
var a = 2
function fn2() {
a++
console.log(a)
}
return fn2
}
var f = fn1()
f() // 3
f() // 4
2
3
4
5
6
7
8
9
10
11
🌰 例子 2️⃣:将函数作为实参传递给另一个函数调用:
function showDelay(msg, time){
setTimeout(function (){
alert(msg)
}, time)
}
showDelay('msg', 2000)
2
3
4
5
6
7
# 闭包的作用
- 使用函数内部的变量在函数执行完后,仍然存活在内存中(延长了局部变量的生命周期)。
- 让函数外部可以操作(读 / 写)到函数内部的数据(变量或者函数)
❓相关问题;
函数执行完后,函数内部声明的局部变量是否还存在?
一般不存在存在。当存在闭包时,可能还存在,因为该局部变量被包含在闭包内。由于外部声明了一个变量把内部的一个闭包一直关联着所以闭包会一直存在(例如上面第一个例子
var f = fn1()
)在函数外部能直接访问函数内部的局部变量吗?
不能。但是通过闭包可以让外部操作它。(例如,通过
return xx()
,可被外部操作。)
# 闭包的生命周期
产生:在嵌套内部函数定义执行完就产生了(⚠️ 不是在调用时)。(例如上第一个例子
fn1()
被定义完,函数提升,内部函数对象已经创建了。)死亡:在嵌套的内部函数成为垃圾对象时(即包含闭包的函数对象称为垃圾对象)。(例如上第一个例子,在要抛弃闭包时,将
f = null
即可。)
注意
实在注意闭包的产生。对比下面的例子:
function fn1(){
var a = 2
var f2 = fn2() { // 此时才产生闭包
a++
console.log(a)
}
return fn2
}
var f = fn1()
f() // 3
f() // 4
2
3
4
5
6
7
8
9
10
11
# 闭包的应用
可以使用闭包定义 JavaScript 模块。
JavaScript 模块:
- 具有特定功能的
.js
文件。- 将所有的数据和功能都封装在一个函数内部(私有的)。
- 只向外暴露一个包含 n 个方法的对象或者函数(封装为对象)。
- 模块的使用者只需要通过模块的对象调用方法来实现相应的功能。
🌰 例子:
:::: tabs cache-lifetime="5" :options="{ useUrlFragment: false }"
::: tab 第一种
- 创建模块
MyModule.js
文件:
function myModule1() {
// 似有数据
var msg = "test"
// 操作数据的函数
function doUpperCase() {
console.log("test")
console.log("doUpperCase: " + msg.toUpperCase());
}
function doLowerCase(){
console.log("doLowerCase: " + msg.toLowerCase());
}
// 向外暴露对象(给外部使用的方法)
return {
doUpperCase: doUpperCase,
doLowerCase: doLowerCase
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- 在测试用
.html
文件中,引入并使用:
<script type="text/javascript" src="myModule1.js"></script>
<script type="text/javascript">
var module = myModule1()
module.doUpperCase()
module.doLowerCase()
</script>
2
3
4
5
6
:::
::: tab ⭐️ 第二种
- 创建模块
MyModule.js
文件:(修改为使用匿名函数)
(function (window) {
// 私有数据
var msg = "test"
// 操作数据的函数
function doUpperCase() {
console.log("doUpperCase: " + msg.toUpperCase());
}
function doLowerCase() {
console.log("doLowerCase: " + msg.toLowerCase());
}
// 向外暴露对象(给外部使用的方法)
window.myModule2 = {
doUpperCase: doUpperCase,
doLowerCase: doLowerCase
}
})(window)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- 在测试用
.html
文件中,引入并使用:
<script type="text/javascript" src="myModule2.js"></script>
<script type="text/javascript">
myModule2.doLowerCase()
myModule2.doUpperCase()
</script>
2
3
4
5
:::
::::
# 闭包的缺点与解决
- 闭包的缺点:
- 函数执行完后,函数内的局部变量没有释放,占用内存的时间会变长。
- 容易造成内存泄漏。
- 解决闭包的缺点:
- 能不使用就不使用闭包。
- 及时释放使用过后的闭包。
🌰 例子:
function fn1() {
var arr = new Array[1000]
function fn2(){
console.log(arr.length)
}
return fn2
}
var f = fn1()
f()
f = null // 让内部函数称为垃圾对象,♻️回收闭包。
2
3
4
5
6
7
8
9
10
11
提示
- 内存溢出:
- 一种程序运行出现的错误。
- 当程序运行需要的内存超过了剩余的内存时(导致崩溃内存不足),就会抛出内存溢出的错误。
🌰 例子:
var obj = {}
for(var i = 0;i< 1000; i++){
obj[i] = new Array(100000) // 明显内存不足以支持处理如此庞大的数据
}
2
3
4
- 内存泄漏:
- 占用的内存没有及时释放。
- 内存泄漏积累多了就容易导致内存溢出。
- 常见的内存泄漏:
- 意外的全局变量。
- 没有及时清理的计时器或回调函数。
- 闭包。
🌰 例子:
// 意外的全局变量
function fn() {
a = new Array(100000) // 定义后没有处理
console.log(a)
}
2
3
4
5
var intervalId = setInterval(function (){
console.log('-')
}, 1000)
// clearInterval(intervalId) // 要及时清理循环定时器
2
3
4
(闭包是使用后没有回收可参考之前提到的 🌰 例子)
# 面试题
⚠️ :https://www.bilibili.com/video/BV14s411E7qf?p=36&spm_id_from=pageDriver
1️⃣
var name = 'The Window'
var object = {
name: 'My Object',
getNameFunc: function() {
return this.name
}
}
alert(object.getNameFunc()())
2
3
4
5
6
7
8
点击查看
'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()())
2
3
4
5
6
7
8
9
10
11
点击查看
"My Object"