我觉得很多人都错误理解了闭包,或者说根本就不理解,只是人云亦云。
维基百科对于闭包的描述:
a closure is a record storing a function together with an environment: a mapping associating each free variable of the function (variables that are used locally, but defined in an enclosing scope)
MDN对于闭包的描述:
闭包是函数和声明该函数的词法环境的组合。
“声明该函数的词法环境”即为函数的作用域链,因此可以说,一切函数都是闭包。
而一般狭义的理解是,闭包就是引用了外部变量的函数和这些变量。
const count = 0
function getCount () {
console.log(count)
}
function run () {
const count = 1
getCount()
}
run()
执行run的时候会发生什么?答案是打印'0',因为run里的getCount函数是一个闭包,包含了其声明时的外部变量count,因此count的值是外层的'0'。
而大多数人,似乎把“利用闭包实现私有变量”当成了闭包本身。
很多讲闭包的文章都会写这个计数器函数的例子:
const increase = function () {
let count = 0
return function () {
return ++count
}
}()
increase() // 1
increase() // 2
他们一般会说“这就是闭包”,却说不清楚到底什么是闭包。
函数内部的匿名函数,和其定义时的外部变量count,作为一个整体,就是闭包。
接下来将这个闭包return并赋值给外层的变量increase, 使该闭包随着increase存在于内存中而不是被GC销毁。count能够完成计数的功能,说明其也存在于内存中,证明了变量也是闭包的一部分。
大多数js库都会用一个自执行的匿名函数封装起来,正是利用了闭包的特性。以jQuery为例:
(function (global, factory) {
...
})(window, function (window) {
var version = "3.2.1"
var support = {}
var siblings = function () { ... }
var jQuery = function () { ... }
jQuery.prototype = { ... }
...
window.jQuery = window.$ = jQuery
})
factory里定义的变量,在其他函数里被引用到,这些函数又成为了jQuery的原型方法,最后jQuery函数和内部的变量作为一个闭包,被“绑定”到window对象上。于是外层环境可以通过window.jQuery使用内部的变量和方法,却不能直接访问和修改。这就实现了私有变量和私有方法,同时还避免了污染全局环境。
闭包的主要用途,正是用来封装私有变量和方法,避免污染全局环境。
类似常见的用法还有函数debounce,throttle等。
实际上除了这些常被提起用法,我们平常也经常用到闭包,举一个实际的例子:
// api.js
const axios = import('axios')
const url = '/getUserInfo'
export function getUserInfo() {
return axios.get(url)
}
// index.js
const { getUserInfo } = import('./api')
getUserInfo().then() ...
函数getUserInfo访问了其外部的变量axios和url,而在index中并未定义这两者,为什么可以直接调用? 这正是因为被导入的getUserInfo是一个“闭包”:包含了函数本身和其作用域链上的变量的一个整体。