在編程語言中,做用域控制着變量與參數的可見性及生命週期,它能減小名稱衝突,並且提供了自動內存管理(javascript 語言精粹)javascript
再者,js不像其餘的編程語言同樣,擁有着塊級做用域,就像下面一段代碼。前端
function afunction(){ var a = 'sf'; console.log(b); console.log(c); var b = function(){ console.log('這是b中的內容'); } function c(){ console.log('這是c中的內容'); } (function d(){ console.log('這是d中的內容'); })() }
實用var聲明的變量和函數聲明將會進行聲明提早在afunction
函數的執行環境中,故上述代碼至關於如下的代碼,在一個變量聲明提早的時候,其值爲undefined
,而函數聲明則是將函數體做爲值。java
function afunction(){ var a; var b; function c(){ console.log('這是c中的內容'); } a = 'sf'; console.log(b); console.log(c); b = function(){ console.log('這是b中的內容'); } (function d(){ console.log('這是d中的內容'); })() }
將上述的代碼稍做改動以下編程
var outer = 'outer'; function afunction(){ function c(){ console.log('這是c中的內容'); } a = 'sf'; console.log(outer); }
咱們在afunction
函數的外部定義了outer
變量,假設這段代碼運行在瀏覽器上,那麼變量提早的過程當中outer
變量被聲明在了window做用域上,也就是瀏覽器中的全局做用域上,而函數中的變量則在函數運行時被聲明在了afunction
做用域上,這個就是局部做用域,在這個局部做用域中,outer
變量被訪問到了,這種跨做用域的讀取變量的形式就是根據做用域鏈來實現的。瀏覽器
在js中,函數也是對象,函數與其餘的對象同樣,擁有能夠訪問的屬性,[[Scope]]
就是其中的一個屬性,它指明瞭哪些東西能夠被函數訪問。
考慮下面的函數閉包
function add(a,b){ var sum = a + b; return sum; }
當函數add
建立時候,add
的[[Scope]]屬性會指向做用域鏈對象,該對象的初始位置指向全局對象,以下圖所示。編程語言
var t = add(1,2);
上述語句執行了add
函數,對於函數的每一次執行,瀏覽器會建立一個執行環境的內部對象,一個執行環境定義了一個函數執行時的環境。函數的每次執行時對應的執行環境都說惟一的。每個執行環境都有本身的做用域鏈,此對象的局部變量,this
, arguments
等組成活動對象,插入在做用域鏈對象的最前端,也就是圖中所示的0號位置,當運行結束後,執行環境和活動對象都將銷燬。
函數的執行過程當中,每遇到一個變量,都會從做用域鏈的頂部,也就是0號位置查找該變量,若是查找成功則返回,查找失敗則按照做用域鏈查找下一個位置的對象,該例子中也就是1號位置的全局對象。模塊化
如上面所討論的那樣,每一次遇到讀取變量的時候,都意味着一次搜索做用域鏈的過程,若是搜索的做用域鏈的層次越多的話,將嚴重影響性能。
因此,當在函數中使用全局變量的時候,所產生的代價是最大的,由於全局對象一直處於做用域鏈的最末位置,讀取局部變量是最快的。
因此,一個提升效率的規則是儘量的使用局部變量。以下面的代碼所示。函數
function demo(){ var d = document, bd = d.body, div = d.getElementsByTagName('div'); d.getElementById('id1').innerHTML = 'aaa'; //(許多使用document,body和div的操做) }
上面的代碼首先將全局的document
對象保存在了局部變量d中,這樣當下次頻繁的使用document
對象時,僅僅須要從局部變量中便可得到。性能
js中實用的是靜態做用域,做用域鏈通常不可改變,可是with
和try-catch
能夠改變做用域鏈,發生在函數的執行時候
function withTest(){ var foo = 'sf'; var obj = {foo:'abc'}; with(obj){ function f(){ alert(foo); } (function(){ alert(foo); })(); f(); } } withTest();
在函數聲明的時候,做用域鏈沒有考慮with
的狀況,當函數執行的時候,動態生成with
的對象,推入在做用域鏈的首位,這就意味着函數的局部變量存在做用域鏈的第二個位置,訪問的代價提升了,雖然訪問with
對象的代價下降了,徹底能夠將with
對象保存在局部變量中,故with
語句不推薦使用。
try{ anErrorFunction(); }catch(e){ errorHandler(e); }
因爲catch
語句中只有一條語句,將error傳遞給errorHandler
函數,因此運行時做用域鏈的改變不會影響性能。
閉包是容許函數訪問局部做用域以外的數據。即便外部函數已經退出,外部函數的變量仍能夠被內部函數訪問到。
所以閉包的實現須要三個條件:
內部函數實用了外部函數的變量
外部函數已經退出
內部函數能夠訪問
function a(){ var x = 0; return function(y){ x = x + y; return x; } } var b = a(); b(1);
上述代碼在執行的時候,b獲得的是閉包對象的引用,雖然a執行完畢後,可是a的活動對象因爲閉包的存在並無被銷燬,在執行b(1)
的時候,仍然訪問到了x變量,並將其加1,若在此執行b(1)
,則x是2,由於閉包的引用b並無消除。
//ul下面有3個li,實現點擊每一個li,彈出li的序號 for(var i = 0,len = lis.length;i < len; i++){ lis[i].onclick = function(i){ return function(){ alert(i); } }(i); }
在這裏,沒有把閉包直接給onclick
事件,而是先定義了一個自執行函數,該函數中包含着閉包的函數,i的值被保存在自執行的函數中,當閉包函數執行後,會從自執行函數中查找i,達到「保存」變量的目的。
注:匿名函數中的this
指向的是window,故在匿名閉包函數使用父函數的this
指針時,須要將其存儲下來,如 var that = this;
模塊化代碼
私有成員
避免全局變量的污染
但願一個變量長期駐紮在內存中
如上面的描述,當執行閉包函數後,父函數所保留下來的活動對象並非在閉包函數的做用域鏈的首位(首位存放的是閉包的活動對象),當頻繁的訪問跨做用域的標識符時候,每次都會形成性能的損失,咱們仍然能夠將經常使用的跨做用域變量存儲在局部變量中,直接訪問該局部變量
IE9及如下的版本使用的是引用計數的內存回收機制,當引用計數爲0的時候將會回收,但有一種循環引用的狀況
window.onload = function(){ var el = document.getElementById("id"); el.onclick = function(){ alert(el.id); } }
這段代碼執行時,將匿名函數對象賦值給el
的onclick
屬性;而後匿名函數內部又引用了el
對象,存在循環引用,因此不能被回收;
(javascript 高級程序設計(第三版))
解決方法:
window.onload = function(){ var el = document.getElementById("id"); var id = el.id; //解除了循環引用 el.onclick = function(){ alert(id); //並無出現循環引用 } el = null; // 將閉包引用的外部活動對象清除 }