闭包

6/30/2020 Javascript

# 概念

什么是闭包

  • 闭包就是能够读取其他函数内部变量的函数
  • 创建闭包的最常见的方式就是在一个函数内创建另一个函数,通过另一个函数访问这个函数的局部变量,利用闭包可以突破作用链域

# 闭包的用途

  1. 读取函数内部的变量:父函数为 f1,子函数为 f2 。f2 可以读取 f1 中的变量
  2. 让这些变量的值始终保持在内存中。不会再 f1 调用后被自动清除。
  3. 方便调用上下文的局部变量。利于代码封装。

    原因:f1 是 f2 的父函数,f2 被赋给了一个全局变量,f2 始终存在内存中,f2 的存在依赖 f1,因此 f1 也始终存在内存中,不会在调用结束后,被垃圾回收机制回收。

  4. 可以让不能接收参数的方法变成可以接收参数,比如: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
1
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 ?
1
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 一直没变化
  • 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 会被继续被改成 2

    • console - 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一致分别是undefined0。此时的 c 被赋值为:{fn(m){return fn(m,1)}} 。不理解的可以重新跑一下 b 模块的 console - 8

    • console - 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

# 闭包的优缺点

# 优点

  1. 延长变量的生命周期(因为变量被子函数引用了,所以不会在父函数被调用后清除)
  2. 让外部函数可以访问内部函数中的变量,因为闭包返回的方法可以赋值给外部的函数,突破作用域的限制

# 缺点

闭包的优点也是闭包的缺点

  1. 变量不会被清除,如此反复操作多次后,将会有很多的变量存在内存中,很容易就会内存溢出。需要手动清除(把变量赋值为 null)
Last Updated: 5/9/2021, 10:45:03 PM