我以爲不少人都錯誤理解了閉包,或者說根本就不理解,只是人云亦云。ios
維基百科對於閉包的描述:axios
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)api
MDN對於閉包的描述:閉包
閉包是函數和聲明該函數的詞法環境的組合。app
「聲明該函數的詞法環境」即爲函數的做用域鏈,所以能夠說,一切函數都是閉包。
而通常狹義的理解是,閉包就是引用了外部變量的函數和這些變量。函數
const count = 0 function getCount () { console.log(count) } function run () { const count = 1 getCount() } run()
執行run的時候會發生什麼?答案是打印'0',由於run裏的getCount函數是一個閉包,包含了其聲明時的外部變量count,所以count的值是外層的'0'。url
而大多數人,彷佛把「利用閉包實現私有變量」當成了閉包自己。prototype
不少講閉包的文章都會寫這個計數器函數的例子:code
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是一個「閉包」:包含了函數自己和其做用域鏈上的變量的一個總體。