javascript
举例详细说明javascript作用域、闭包原理以及性能问题(转)
轉自:http://www.cnblogs.com/mrsunny/archive/2011/11/03/2233978.html
這可能是每一個jser都曾經為之頭疼的卻又非常經典的問題,關系到內存,關系到閉包,關系到javascript運行機制。關系到功能,關系到性能。
文章內容主要參考自《High Performance JavaScript》,這本書對javascript性能方面確實講的比較深入,大家有空都可以嘗試著閱讀一下,下載地址:中英電子版。
復習,筆記,更深入的理解。
歡迎拍磚指正。
作用域:
下面我們先搞明白這樣幾個概念:
- 函數對象的[[scope]]屬性、ScopeChain(作用域鏈)
- Execution Context(運行期上下文)、Activation Object(激活對象)
[[scope]]屬性:
javascript中每個函數都是一個函數對象(函數實例),既然是對象,就有相關的屬性和方法。[[scope]]就是每個函數對象都具有的一個僅供javascript引擎內部使用的屬性,該屬性是一個集合(類似于鏈表結構),集合中保存了該函數在被創建時的作用域中的所有對象,而這個作用域集合形成的鏈表則被稱為ScopeChain(作用域鏈)。
該作用域鏈中保存的作用域對象,就是該函數可以訪問的所有數據。例如(例子引用自《High Performance JavaScript高性能javascript》):
function add(num1, num2){ var sum = num1 + num2; return sum; } 圖 1當add函數被創建時,函數所在的全局作用域的全局對象被放置到add函數的作用域鏈([[scope]]屬性)中。我們可以從圖1中看到作用域鏈的第一個對象保存的是全局對象,全局對象中保存了諸如this,window,document以及全局對象中的add函數,也就是他自己。這也就是我們可以在全局作用域下的函數中訪問window(this),訪問全局變量,訪問函數自身的原因。當然還有函數作用域不是全局的情況,等會兒我們再討論。
Execution Context(運行期上下文)、Activation Object(激活對象):
(前天看了老羅的演講,老羅說過年的時候給全公司的人每人發一臺電冰箱,要給校舍的所有的廁所門上都安上新鎖,保證童鞋們能有個真正隱私的地方。)
var total = add(5, 10);
當開始執行此函數時,就會創建一個Execution Context的內部對象,該對象定義了函數運行時的作用域環境(注意這里要和函數創建時的作用域鏈對象[[scope]]區分,這是兩個不同的作用域鏈對象,這樣分開我猜測一是為了保護[[scope]],二是為了方便根據不同的運行時環境控制作用域鏈。函數每執行一次,都會創建單獨的Execution Context,也就相當于每次執行函數前,都把函數的作用域鏈復制了一份到當前的Execution Context中)。Execution Context對象有自己的作用域鏈,在Execution Context創建時初始化,會將函數創建時的作用域鏈對象[[scope]]中的全部內容按照在[[scope]]作用域鏈中的順序復制到Execution Context的作用域鏈中。
此時,在Execution Context的作用域鏈的頂部會插入一個新的對象,叫做Activation Object(激活對象),這個激活對象又是干嘛的呢?這個激活對象保存了函數中的所有形參,實參,局部變量,this指針等函數執行時函數內部的數據情況,這個Activation Object是一個可變對象,里面的數據隨著函數執行時的數據的變化而變化,當函數執行結束之后,就會銷毀Execution Context,也就會銷毀Execution Context的作用域鏈,當然也就會銷毀Activation Object(但如果存在閉包,Activation Object就會以另外一種方式存在,這也是閉包產生的真正原因,具體的我們稍后討論。)。具體情況如圖所示:
圖 2
我們從左往右看,第一部分是函數執行時創建的Execution Context,它有自己的作用域鏈,第二部分是作用域鏈中的對象,索引為1的對象是從[[scope]]作用域鏈中復制過來的,索引為0的對象是在函數執行時創建的,第三部分是作用域鏈中的對象的內容Activation Object和Global Object。
函數在運行過程中,沒遇到一個變量,都會去Execution Context的作用域鏈中從上到下依次搜索,如果在第一個作用域鏈(假如是Activation Object)中找到了,那么就返回這個變量,如果沒有找到,那么繼續向下查找,直到找到為止,這也就是為什么函數可以訪問全局變量,當局部變量和全局變量同名時,會使用局部變量而不使用全局變量,以及javascript中各種看似怪異的、有趣的作用域問題的答案(你可以用這種方法來解釋你以前碰到的所有作用域問題,當然,如果還是有疑問的話,非常希望你能貼出代碼,我們一起討論。)
一般情況下,一個函數的作用域鏈是不會在函數運行時被改變的,但有些運算符會臨時改變作用域鏈,with和try catch的catch子句。看下面的例子:
function initUI(){ with (document){ //avoid! var bd = body, links = getElementsByTagName("a"), i= 0, len = links.length; while(i < len){ update(links[i++]); } getElementById("go-btn").onclick = function(){ start(); }; bd.className = "active"; }//eOf with }上例中,在onclick事件的事件處理器中引用了外部函數assignEvents的局部變量id,形成了閉包,下面我們看一下它們的作用域圖示:
圖 4
我們一起來從作用域的角度分析一下閉包的形成過程:
圖 5
這也就是閉包為何能“記得”在它周圍到底發生了什么,為何閉包能訪問外層函數的局部數據,為何閉包能保持這些局部數據而不在外層函數執行完畢銷毀時一起銷毀等等的原因。
前些天一個前輩(Darrel文叔)告訴我一句話,一針見血:沒有內存,就沒有閉包。
性能問題:
在作用域鏈和閉包中的性能問題主要表現在數據讀寫的速度上。
由于作用域鏈的原因,我們訪問全局作用域的數據(這里為什么不說變量呢?因為不僅包括變量,還有函數,對象等其他內容)時,效率是最低的,而訪問局部數據時的效率是最高的。
所以一個非常經典的解決數據訪問性能問題的方案出現了:將需要訪問的數據盡量的以局部數據的方式緩存起來。這樣當標識符解析程序在作用域鏈中尋找數據時,直接就可以在作用域鏈的最上層找到想要的數據,效率自然就提升了。
這句話可以解決很多性能問題:設置緩存,將數據保存在局部變量中。
轉載請注明出處:
參考:
- 《高性能javascript》——Zakas(劉新 譯)
- 李松峰《理解javascript閉包》
- 阮一峰《學習javascript閉包》
- 網絡各種雜文
轉載于:https://www.cnblogs.com/jintianfan/p/3475470.html
《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀總結
以上是生活随笔為你收集整理的举例详细说明javascript作用域、闭包原理以及性能问题(转)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 简单的对象监听器 观察者设计模式
- 下一篇: [译] 第五天: GruntJS - 为