闭包
# 概念
什么是闭包
- 闭包就是能够
读取其他函数内部变量的函数
- 创建闭包的最常见的方式就是在
一个函数内创建另一个函数
,通过另一个函数访问这个函数的局部变量,利用闭包可以突破作用链域
# 闭包的用途
- 读取函数内部的变量:父函数为 f1,子函数为 f2 。f2 可以读取 f1 中的变量
- 让这些变量的值始终保持在内存中。不会再 f1 调用后被自动清除。
- 方便调用上下文的局部变量。利于代码封装。
原因:f1 是 f2 的父函数,f2 被赋给了一个全局变量,f2 始终存在内存中,f2 的存在依赖 f1,因此 f1 也始终存在内存中,不会在调用结束后,被垃圾回收机制回收。
- 可以让不能接收参数的方法变成可以接收参数,比如:vue 中的
computed
export default {
computed: {
/**
* 判断tab链接是否高亮
*/
isCurrPath() {
return (path = window.location.href) => {
// 获取当前页面的路由。只取path。path不带任何参数。匹配
let currHref = this.$router.history.current.path.toLowerCase()
return path.toLowerCase().indexOf(currHref) !== -1
}
}
}
}
// 使用 {{isCurrPath('url')}} 返回ture就是高亮。false就是非高亮tab
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 先来一个题目
// fn 函数中有console 调用就会打印对应的值
// 请写出下面的输出结果
function fn(n, o) {
console.log(o)
return {
fn: function(m) {
return fn(m, n)
}
}
}
var a = fn(0) // 1. console = ?
a.fn(1) // 2. console = ?
a.fn(2) // 3. console = ?
a.fn(3) // 4. console = ?
var b = fn(0) // 5. console = ?
.fn(1) // 6. console = ?
.fn(2) // 7. console = ?
.fn(3) // 8. console = ?
var c = fn(0).fn(1) // 9. console ? 10 .console ?
c.fn(2) // 11. console ?
c.fn(3) // 12. console ?
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
查看答案
a 对应编号 | a 打印的值 | b 对应编号 | b 打印的值 | c 对应编号 | c 打印的值 | ||
---|---|---|---|---|---|---|---|
1 | undefined | 5 | undefined | 9 | undefined | ||
2 | 0 | 6 | 0 | 10 | 0 | ||
3 | 0 | 7 | 1 | 11 | 1 | ||
4 | 0 | 8 | 2 | 12 | 1 |
全对了吗?恭喜可以跳过这块了~
查看解析
a 模块
- console - 1 :执行
a.fn(0)
=>o
的确不存在。返回了一个对象:a = { fn (m) { return fn(m,0) } }
。
注意
n 被改成了 0
。 因为内部函数可以访问外部函数的变量,理解这一点才能继续执行下去!!- console - 2 :执行
a.fn(1)
= 执行fn(1)
=> 执行了fn(1,0) = [fn(m,n) 这时候的 fn是最外层的fn]
=> 所以打印的 n 就是参数 0。这时候的O
来自第一次执行a.fn(0)
因为这是闭包特性之一:内部函数可以访问外部函数的变量,第一次执行 a.fn(0) 的
参数n
被保留了下来!注意这里虽然有返回值,可是并没有地方接收。a 还是之前的对象
- console - 3 :既然 a 没有变化。那继续执行
a.fn(2)
= 执行fn(2)
=> 执行了fn(2,0)
=> 打印的还是0
。原因同上:n 被保留了下来!并且没有重新赋值 - console - 4:理由同 3。n 一直没变化
- console - 1 :执行
b 模块
a 与 b 最大的不同在于:a 是分别调用,就算最后的
fn(m,n)
返回了对象,可是 a 并没有接收,a 一直都是一开始的对象:a = { fn (m) { return fn(m,n) } }
而 b 则是
链式调用
。上一步的返回值会被下一次调用的时候用上。直接分析代码:console - 5:第一步运行
b = fn(0)
=> o 不存在,undefined。 注意因为是链式调用,此时 b 还没被正确赋值,返回的对象:{ fn (m) { return fn(m,0) } }
会直接被步骤 6 接着执行console - 6:这时候执行
fn(1)
的fn
来自console-5
的返回值 => 执行fn(1)
时会执行fn(m,n)
= 执行fn(1,0)
0 也是来自第一步的fn(0)
。因为闭包把外部函数的 n 保留了下来。
与此同时 执行
fn(1,0)
又返回了一个对象!{fn(m){ return fn(m,1)}}
。变量 n = 1,在父函数(fn(1,0))传入的console - 7:链式调用的原因,所以执行
fn(2)
的时候会接着步骤 6 返回的对象的 fn,就会执行到:fn(2,1)
此时的变量 o = 1
来自与 console - 6 的fn(1,0)
。所以 输出的变量o
的值便是 1。并且 n 会被继续被改成 2console - 8:理由同上,上一步骤的 n 被修改,并且保存了下来,所以执行
fn(3)
相当于执行了fn(3,2)
。打印的值便是 2 。这时候链式调用的最后一个已经被执行完,b 被赋值为 :{fn(m) return fn(m,3)}
。可是已经没有后续操作了,
b 模块结束。注意理解为什么最后是
fn(m,3)
。这是贯穿这一题的精髓!c 模块
c 模块与 a 和 b 的区别为,结合了他们 2 者的模式,既有闭包,也有链式调用,链式调用后赋值给 c。c 在继续执行了 2 次 fn
console - 9、console - 10:这个链式调用和 b 模块的一致,输出结果和
5,6
一致分别是undefined
和0
。此时的 c 被赋值为:{fn(m){return fn(m,1)}}
。不理解的可以重新跑一下 b 模块的 console - 8console - 11:看上一步返回的
{fn(m){return fn(m,1)}}
所以执行c.fn(2)
会接着执行fn(2,1)
。所以重新打印的是变量o = 1
。这里 fn(2,1)虽然有返回值,可是 c 并没有接收,所以 c 并没有被修改!参照 a 模块的原理console - 12:上一步也说了。既然 c 没有被修改,那么 c 还是
{fn(m){return fn(m,1)}}
。所以执行c.fn(3)
。会触发fn(3,1)
。打印的变量就还是 1
# 闭包的优缺点
# 优点
- 延长变量的生命周期(因为变量被子函数引用了,所以不会在父函数被调用后清除)
- 让外部函数可以访问内部函数中的变量,因为闭包返回的方法可以赋值给外部的函数,突破作用域的限制
# 缺点
闭包的优点也是闭包的缺点
- 变量不会被清除,如此反复操作多次后,将会有很多的变量存在内存中,很容易就会内存溢出。需要手动清除(把变量赋值为 null)