头图

关于无处不在的闭包

xizugogo
闭包是函数和函数的词法环境的引用的组合 [MDN]
闭包是指有权访问另一个函数作用域中的变量的函数 [JS高级程序设计]
闭包是内部函数引用外部函数的变量的集合 [李兵]
闭包是一个绑定了执行环境的函数 [程劭非]

1. 什么是闭包

欲懂闭包,必先知道作用域、作用域链的含义。那么我们就从作用域开始讲起。

(1)作用域 = 去哪里找变量

函数执行时会形成调用栈,调用函数时其执行上下文(其中保存了:变量环境和词法环境)入栈,执行完毕出栈。执行上下文存储着函数中声明的变量,函数执行也会访问其他函数中声明的变量。在函数执行查找变量时,变量的可见范围就是作用域,查找变量的链路就变量的作用域链
查找变量时遵循两个规则:第1,函数嵌套时内部函数总能访问外部函数中声明的变量;第2,调用栈中的函数,总是先在自身的执行上下文找变量,再到最外层的全局执行上下文去找

(2)闭包 = 函数 + 变量

定义:持有着其它作用域中变量的函数 及其持有的其它变量的集合 的组合
  • 闭包产生的过程:闭包通常产生于函数嵌套时。外部outer函数声明了变量o,内部函数inner可以访问到o。当inner函数被作为返回值被outer返回, outer函数执行完毕之后,outer的执行上下文就弹出了调用栈。此时变量o应该被GC回收掉,但由于inner函数此时还引用时变量o, o并没有随着outer的执行完毕而销毁,此时我们称产生了闭包。
  • 闭包产生的原因:即使外部函数已经执行完,由于内部函数持有外部函数中变量的引用,外部函数作用域的变量依然存在在内存之中
产生闭包之后,变量的查找链路就会发生变化。首先在自身的执行上下文找变量,再到闭包中去找,最后再到最外层的全局执行上下文去找

2. 闭包的特征

(1)函数的返回值是函数
(2)函数作为参数传递给另外的函数

回调函数都是闭包,因为函数被当做参数传递了

3. 闭包的使用场景

根据上述第2点,闭包的特征还是比较明显的。本节介绍闭包的具体应用场景。
(1)所有使用到回调函数的地方都是闭包的应用,如事件监听回调、Promise绑定的then回调、数组的map()函数;
(2)利用闭包模仿块级作用域、封装私有变量、延长局部变量的寿命;
(3)闭包和setTimeOut()结合实现防抖、节流;
(4)函数柯里化:多参数函数转化成单参函数,好处是可以进行参数复用。

// 普通的add函数
function add(x, y) {
    return x + y
}
// Currying后
function curryingAdd(x) {
    return function (y) {
        return x + y
    }
}
add(1, 2)           // 普通函数调用3
curryingAdd(1)(2)   // 柯里化函数调用3

4. 注意事项

闭包会携带包含其它函数的作用域,因此会比其他函数占用更多的内存,容易导致内存泄漏。如果闭包会一直使用,那么它可以作为全局变量而存在;但如果使用频率不高,而且占用内存又比较大的话,那就尽量让它成为一个局部变量。

总结

本文以变量的查找作为出发点,引出了变量作用域、作用域链、函数的调用栈等概念。然后介绍了闭包的组成、形成过程。闭包听起来玄妙,用起来简单。文中还总结了闭包的典型特征,有助于我们在实际的开发过程中发现闭包、理解闭包。最后,还介绍了闭包的具体使用场景,也给出了其缺点、使用注意事项。

参考:
https://juejin.cn/post/694469...
https://www.cnblogs.com/gg-qq...
https://time.geekbang.org/col...

阅读 302

千里之行,始于足下

18 声望
1 粉丝
0 条评论
你知道吗?

千里之行,始于足下

18 声望
1 粉丝
宣传栏