概念: 有權訪問另外一個函數做用域中的變量的函數 優勢: 內存駐留、避免全局變量污染 缺點: 內存泄漏(?)、沒法預知變量被更改 相關知識點: 做用域、內存駐留、內存泄露、JS執行機制、內存機制、垃圾回收機制
要理解什麼是閉包、首先咱們要對JS中的做用域
和做用域鏈
有必定的理解:前端
做用域: 簡單來說就是一個變量可以被訪問的範圍,JS有三種做用域,分別是:全局做用域、函數做用域、塊級做用域 做用域鏈: 在JS中做用域是一層一層嵌套的, 子做用域中能夠訪問父做用域中的變量, 咱們把這種能夠一層一層向上訪問的鏈式結構叫作做用域鏈.
~~ 當JS建立一個函數時, 首先會建立一個預先包含全局變量對象的做用域鏈
, 保存在內部的Scope
屬性之中
當JS調用這個函數時, 會爲此函數建立一個執行環境
, 也就是函數上下文
而後複製函數的Scope
的屬性中的對象構建執行環境的做用域鏈
~~瀏覽器
做用域與做用域鏈就比如是數學中的集合, 最大的即是全局做用域, 子集即是函數做用域, 子集中又能夠有子集,全部的本身均可以向外訪問,但全部的父級不能夠向子集訪問,相同的子集之間也不能夠互相訪問。安全
有權訪問另外一個函數做用域中的變量的函數 《JavaScript高級程序設計》閉包
咱們來理解一下這句話異步
那麼怎麼纔有權訪問另外一個函數做用域中的變量呢?
根據上文中子做用域中能夠訪問父做用域中的變量
的特性,答案是: 成爲另外一個函數的子函數函數
在《JavaScript權威指南》, 強調了函數體內部變量能夠保存在函數做用域 函數對象能夠經過做用域鏈相互關聯起來,函數體內部變量能夠保存在函數做用域內,這就是閉包。
性能
從嚴格的角度來說, 閉包須要知足三個必要的條件:學習
所以咱們猜測一個閉包的樣子, 大概應該是這樣的:線程
// 全局變量-全局做用域 var global = "global scope"; function partner() { // 局部變量-函數做用域 var variable = 'Function scope'; function children() { console.log(variable); } } // 此時子函數 children 訪問了父函數 partner, 咱們就稱子函數 children 爲閉包.
意義: 內存駐留
當咱們要實現一個計數器時, 首先用常規的方法來寫:設計
// 計數器 var count = 0; function counter() { console.log(count++); } counter(); // 1 counter(); // 2
上面的代碼已經實現了咱們所需的功能, 可是它並不完美, 一方面全局的count
變量可能形成變量污染, 另外一方面代碼中的任何一個位置均可以輕鬆的修改這個count的值, 這是咱們所不能接受的!
所以, 咱們須要一個變量能夠在counter
函數中訪問, 但它並不在全局做用域中, 且能夠長時間的停留在內存當中不被瀏覽器的垃圾回收機制
清除, 因而咱們就想到了閉包, 接下來咱們用閉包再實現一下計數器:
// 計數器 var counter = (function() { var count = 0; return function() { console.log(count++); } })() counter(); // 1 counter(); // 2
閉包彷彿結合了全局做用域與局部做用域的優勢與一身,對於其餘函數做用域而言,父函數做用域中的變量就像是父函數和閉包的一個 「私有變量」 , 而對於父函數和閉包而言, 父函數做用域中的變量又好像身處 「全局做用域」 中.
缺陷: 影響性能、變量修改
閉包的存在會致使函數中得變量一直存在於內存中,不能被垃圾回收機制清理, 致使內存消耗增長, 影響系統運行的性能,因此不能濫用閉包.
若是要使用閉包, 應該在使用結束時手動的清除閉包!
在閉包存在的時候,將父函數比作一個類,父函數中得局部變量就是類的私有屬性,而閉包訪問的變量就是類的公共屬性,在父函數做用域和閉包函數中均可以對 variable 變量進行修改, 這是件使人頭疼的事情, 由於你並不知道有多少閉包會在何時對你的變量進行修改, 這將形成你的程序極不穩定甚至執行異常.
function parent() { let name = 'sf' return { get() { return name }, set(val) { name = val } } } const nameProxy = parent() console.log(nameProxy.get()) // 'sf' // 子函數children也能夠定義爲null在全局,而後在parent中賦值
// 由於setTimeout是異步的, 代碼執行先同步後異步, 因此當它執行的時候for循環已經結束了 for(var i=0,len=10; i<len; i++) { setTimeout(() => console.log(i), 1); } // 10個10 // 閉包寫法-IIFE for(var i=0,len=10; i<len; i++) { ((i) => { setTimeout(() => console.log(i), 1); })(i) }
// 內部的變量不會污染全局變量, 能夠放心的對多個模塊進行合併 (function(window) { let a = 1 let b = '2' function add() { a++ } })(window)
執行父函數時, JS線程會對內部的子函數進行預編譯, 看一看子函數中是否用到了父函數的內部變量
若是用到了, 爲了保證在將來調用子函數時不出錯, JS線程會在父函數執行完畢以後, 清空函數執行棧中的上下文以前, 將父函數中被用到的變量 copy 一份放在堆中, 供以後子函數引用.
內存泄露是指一塊被分配的內存既不能使用,又不能回收,直到瀏覽器進程結束。
在閉包形成的影響中,咱們常常會聽到一句話, 那即是:在IE中閉包的使用可能會致使內存泄漏。可是,在我學習V8引擎的過程當中發現,引起內存泄漏的緣由彷佛是循環引用,這讓我對閉包和內存泄漏的關係產生了疑惑.
後續我將圍繞內存泄漏
從新整理一篇文章,這裏先引用一篇文章中的一句話和一個例子:
在IE瀏覽器中,因爲BOM和DOM中的對象是使用C++以COM對象的方式實現的,而COM對象的垃圾收集機制採用的是引用計數策略。在基於引用計數策略的垃圾回收機制中,若是兩個對象之間造成了循環引用,那麼這兩個對象都沒法被回收,但循環引用形成的內存泄露在本質上也不是閉包形成的。 做者:前端小學生\_f675 連接:https://www.jianshu.com/p/66881ba3c8ba 來源:簡書 著做權歸做者全部。商業轉載請聯繫做者得到受權,非商業轉載請註明出處。
// 內存泄漏 var ele = document.getElementById("someElement"); ele.click = function() { console.log(ele.id); } // 解決方案:使用結束後釋放內存 var ele = document.getElementById("someElement"); var eleID = ele.id; ele.click = function() { console.log(eleID); } ele = null;
文章地址: 待更新