閉包是有權訪問另外一個函數做用域中的變量的函數,好比:閉包
function createFunc(words) { return function() { return words; } } var func = createFunc("Hello World!") func(); // "Hello World!"
上述例子中createFunc
的返回值是一個函數(閉包),這個返回值在調用時仍然能夠訪問createFunc
的words
屬性,這是爲何呢?還記得在以前的文章Javascript 變量、做用域和內存問題中提到的,一個函數在建立時,會生成一個內部屬性[[scope]]
,這個屬性包含函數被建立的做用域中對象的集合,也就包括了createFunc
的活動對象,而若是沒有閉包,createFunc
的活動對象在調用結束時就能夠進入GC序列,只有銷燬對閉包的引用,即func = null
,纔會使createFunc
的活動對象被GC。
總結閉包的做用域鏈以下圖:函數
經過上述分析咱們還能夠看出,閉包有一個不一樣於普通函數的特性,就是它會攜帶包含它的函數的做用域,所以會佔用更多內存。學習
閉包的做用域鏈決定了閉包只能包含外部函數中任何變量的最終值,舉個例子:this
function createFunctions(){ var result = new Array(); for (var i=0; i < 10; i++){ result[i] = function(){ return i; }; } return result; } var result = createFunctions(); result[3](); // 10
上述例子中,咱們在createFunctions
函數中,經過for
循環,建立了多個函數,並指望每個都能返回建立它時的索引值,但結果發現,每個函數都只能返回i
的最終值(因爲ECMAScript中沒有塊級做用域,所以i
是createFunctions
中的局部變量),之因此是這樣的結果,是由於每個閉包可以訪問到的i
都是對局部變量i
的引用,因爲這種狀況的存在,所以咱們發現不少閉包都是這樣寫的:spa
function createFunctions(){ var result = new Array(); for (var i=0; i < 10; i++){ result[i] = function(num){ return function() { return num; } }(i); } return result; } var result = createFunctions(); result[3](); // 3
原理就是將動態的局部變量參數化,這樣每個閉包都保存了該局部變量某個時刻的副本。code
this
this
是基於執行環境綁定的,若是是全局函數,那麼this
指window
,若是是某個對象的函數,那麼this
指這個對象,而匿名函數的執行環境具備全局性,所以閉包中的this
通常指window
,好比:對象
var name = "The Window"; var object = { name : "My Object", getNameFunc : function(){ return function(){ return this.name; }; } }; console.log(object.getNameFunc()()); //"The Window"
上述例子中,閉包是在全局環境中執行的,而咱們知道,每一個函數在執行時會基於執行環境自動得到this
,所以this
指向了window
。
上述例子中,如何使閉包能夠訪問object呢,能夠作以下修改:索引
var name = "The Window"; var object = { name : "My Object", getNameFunc : function(){ var that = this; return function(){ return that.name; }; } }; object.getNameFunc()() // "My Object"
在以前的文章Javascript 變量、做用域和內存問題提到過IE在版本9以前,ECMAScript對象和DOM對象的GC機制不一樣,循環引用會致使DOM對象永遠不能被回收,學習完這一章節後才發現本身常常寫的一段代碼就存在這樣的問題!-_-,書中也提到了這個例子:事件
function assignHandler(){ var element = document.getElementById("someElement"); element.onclick = function(){ console.log(element.id); }; }
上面的代碼就建立了一個閉包做爲element
的事件處理程序,這裏的循環引用體如今element
的屬性onclick
的值中存在對element
的引用,即便退出assignHandler
,element
這個DOM對象也不會被引用計數機制GC,那麼不在閉包中顯式地引用element
,總能夠了吧,就好比:ip
function assignHandler(){ var element = document.getElementById("someElement"); var id = element.id; element.onclick = function(){ console.log(id); }; }
答案是不行的,由於閉包中不管如何都是要保存一份對 assignHandler
活動對象的引用的,天然包含element
。
以前提到過,因爲閉包中保存的只是函數活動對象的引用,那麼閉包中可以訪問的變量就具備動態性,上個例子中,閉包因爲引用了assignHandler
的活動對象,就引用了element
,而element
引用了一個DOM對象,那麼,若是element
不引用DOM對象,而是其餘對象,好比null
,那麼element
就能夠被標記清楚機制GC。
function assignHandler(){ var element = document.getElementById("someElement"); var id = element.id; element.onclick = function(){ console.log(id); }; element = null; }