我理解的閉包

来源:http://www.cnblogs.com/huanglei-/archive/2017/08/29/7071202.html
-Advertisement-
Play Games

網上關於閉包的文章一搜一大堆,但是我還是要來說一下我的理解。 我理解的閉包,其實就是 訪問了外部變數的函數 : 可能和同學們平常看到的理解不太一樣,但 "維基百科" )的確是這樣描述的: a closure is a record storing a function together with a ...


網上關於閉包的文章一搜一大堆,但是我還是要來說一下我的理解。

我理解的閉包,其實就是訪問了外部變數的函數

let a = 0
function b() {
  console.log(a)
}

可能和同學們平常看到的理解不太一樣,但維基百科的確是這樣描述的:

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)
閉包就是一個引用了外部變數的函數以及其運行環境的統稱

平常我們提到的“閉包”,都是通過返回一個函數來讓外部能訪問函數內部的變數,但我認為這隻是閉包的一種應用罷了。前面的例子是閉包,但是它已經是全局環境了,不能再被指向給上一層環境,因此函數執行完成後該閉包便被銷毀了。

下麵讓我們以一個簡單的計數器函數為例,更加深入的去理解閉包:

/*不使用閉包*/
function add () {
  let count = 0;
  return counter += 1;
}
add() //1
add() //1
add() //1

/*使用閉包*/
let add = function plus ()  {
  let count = 0
  return function closure () {
      return count += 1
  }
}()
add() // 1
add() // 2
add() // 3

不使用閉包的情況下,每次執行add()函數時,局部變數count值都會被初始化為0,並不能起到計數器的作用。

使用閉包的情況,本身是兩個匿名函數,為了方便描述我給它們分別命名為plus()和closure()。plus內定義了一個變數count,然後返回了closure函數,這個函數引用了count變數。函數最後一個()讓其執行後,將其結果賦值給了一個全局變數add。

下麵我們看下調用add函數會發生什麼:調用add函數即調用closure,首先會執行count+=1,而closure函數內部是沒有定義‘count’這個變數的,於是它會循著作用域鏈往上查找,到plus 函數找到了count變數,然後取得count的值,計算並返回。再次執行,可以看到count值是繼續遞增的,說明count被保存在了記憶體中,但卻不能直接訪問。

讓我們看一下閉包是怎樣工作的:closure函數引用了其外部變數count,此時closure函數(以及其運行環境和外部變數)形成一個閉包,並將整個閉包返回,賦值給了一個全局變數。於是整個閉包隨著add留在記憶體中,直到add與該閉包的連接被清除(將add指向別處或者 add = null),該閉包占用的記憶體才會被回收。

閉包的應用1

閉包最常見的應用,是各種js庫用來封裝源碼。以jQuery為例,源碼核心結構如下:

(function (global, factory) {
  ...
})(window, function (window) {
  var arr = []
  var document = window.document
  ...
  var jQuery = function (selector, context) {
    ...
  }
  ...
  window.jQuery = window.$ = jQuery
})

我們來解讀一下這裡的閉包:首先,匿名函數內定義了各種變數,然後jQuery對象(同時也是一個函數)及其屬性和方法引用到了這些變數。最後用window.$ = jQuery將jQuery對象和全局對象連接起來---因此這個閉包只會在其全局對象被銷毀(頁面或iframe被關閉),或者連接被切斷(window.$ = null)時才會銷毀。這就是閉包最常見的應用---利用匿名自執行函數的作用域,將內部變數封裝起來,防止被外部修改,也避免了污染全局變數環境。

看到這裡也明瞭,並不是“return一個函數才叫閉包”,前面計數器的例子也可以改成這樣:

(function closure(global) {
  let count = 0
  function plus() {
    return count += 1
  }
  global.add = plus
})(window)

閉包的應用2

平常我們可能需要在window.onresize中改變頁面樣式,用戶輸入字元時ajax遠程搜索等。由於這類事件會在短時間內多次觸發,不加以控制則會頻繁調用處理程式,影響性能。我們可以利用閉包,來實現函數節流(throttle)和函數去抖(debounce),提高頁面性能。

函數節流:預先設定一個執行周期,當調用方法的間隔大於執行周期則執行該方法,然後記錄當前執行的時間併進入下一個新周期。

const throttle = function (fn, ms) {
  let timestamp = 0
  return function () {
    let current = Date.now()
    if (current - timestamp > ms) {
      fn.apply(this, arguments)
      timestamp = current
    }
  }
}

setInterval(throttle(function (arg) {
  console.log(arg)
}, 2000).bind(this, 'hello'), 50)

利用閉包將timestamp 的值保存起來,以記錄函數上次的調用時間。如果小於時間間隔則不處理,如果大於間隔則執行函數並記錄這一次執行的時間。我們用setInterval模擬一下連續觸發的情況,可以看到雖然setInterval的間隔設置為50,但是函數的執行間隔仍然是由throttle設定的間隔控制的---2s觸發一次。這個方法也適用於防止用戶連續點擊按鈕發起重覆請求的情況。

函數防抖:有一個形象的比喻是,如果用手指一直按住一個彈簧,它將不會彈起,直到你鬆手為止。也就是說當調用函數n毫秒後,才會執行該函數,若在這n毫秒內又調用此函數,則將重新計算時間。

var debounce = function(fn, ms){
  var timeoutID
  return function(){
    clearTimeout(timeoutID)
    timeoutID = setTimeout(() => {
      fn.apply(this, arguments)
    }, ms)
  }
}

setInterval(debounce(function (arg) {
  console.log(arg)
},300).bind(this,'world'),50)

這次我們用閉包保存了setTimeout返回的ID。每當函數執行的時候,就重新設置timeout,因此50ms間隔的interval遇上300ms間隔的debounce,函數將永不會執行,直到取消interval。這種方法適用於頁面resize,文字輸入遠程搜索等情況,但是由於是延遲觸發,不適合用於按鈕點擊等交互體驗明顯的地方。

總結

作用域的限制,讓外部環境不能訪問內部變數,而內部環境可以訪問其作用域鏈上的外部變數;當一個函數訪問了其外部變數,這個函數就同這些被訪問的變數形成了閉包;如果將這個閉包同外層環境連接起來,這個閉包將會一直存在記憶體中,直到外層環境銷毀;而外層環境可以利用閉包函數,訪問閉包中保存的變數。

由於閉包會讓函數及變數一直留在記憶體中,不能被GC機制回收,因此當不再使用該閉包時,應當及時將該閉包與當前環境的連接清除,以便記憶體被系統回收。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 生產者與消費者模式是《設計模式》的書籍中推薦的23種模式之一,下麵我們根據示例實現此模式的代碼例子: 生產者A: //生產者A public class ProducerA extends Thread{ // 生產一個商品 private void produceGoods() { String ...
  • 目前,不管是前端開發人員還是個人站長,經常需要一些代碼處理類的工具,比如:代碼對比、代碼格式化、圖標製作等。有時就是一時急用可電腦上又沒有安裝相關的軟體,這裡為大家收集了一些我們經常會用到的線上工具。 代碼對比/歸併: http://www.matools.com/compare 正則表達式: ht ...
  • location對象其實就是window.location,其中頂級對象window可以省略 主要的屬性 location.host 主機 location.hostname 主機名 location.port 埠 location.protocol 協議 location.pathname 路徑 ...
  • 在jquery中,插件開發常見的有: 一種是為$函數本身擴展一個方法,這種是靜態擴展(也叫類擴展),這種插件一般是工具方法, 還有一種是擴展在原型對象$.fn上面的,開發出來的插件是用在dom元素上面的 一、類級別的擴展 註意要提前引入jquery庫, 上例在$函數上面添加了一個方法showMsg, ...
  • ES6引入了Class(類)這個概念,作為對象的模板,通過class關鍵字,可以定義類。基本上,ES6的class可以看作只是一個語法糖,它的絕大部分功能,ES5都可以做到,新的class寫法只是讓對象原型的寫法更加清晰、更像面向對象編程的語法而已。 那麼如何掌握類這項技能,讓我來陪大家一起學習: ...
  • 今天做項目時,做到註冊頁面,有一個需求是:要求用戶設置的密碼不包含全部或部分用戶名(任意連續3個字元)。 無奈js薄弱的很,研究了好久才寫出來,汗顏。。。。 //截取字元串中相鄰的三個字元,並放到新建的數組中 function subStr (str) { var arr = []; for(var ...
  • DIV+CSS clear both清除產生浮動 我們知道有時使用了css float浮動會產生css浮動,這個時候就需要清理清除浮動,我們就用clear樣式屬性即可實現。 接下來我們來認識與學習css clear知識與用法。 一、clear語法與結構 - TOP 1、clear語法:clear : ...
  • spectre.css是一個輕量級的css框架,只要使用過bootstrap,那麼這個使用起來就不是問題,不過現在網路上的文檔大都是英文,而且寫的比較概括,所以查閱起來不是很方便,今天是我第一天學習這個框架,把所學到的知識點記錄下來。 使用步驟: 1.引用方法 下載spectre.css,地址:ht ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...