上篇文章裡我結束瞭對象的創建的內容,最後引出瞭作用域鏈和執行環境的問題。當我對這塊知識有瞭更深入的瞭解後,回頭看看jQuery源碼才知道大師們寫的代碼是如何的厲害,jQuery源碼裡很好的運用瞭作用域鏈和執行環境的知識來提升程序性能。
好瞭,不廢話瞭,上篇博文裡對作用域講的比較簡略。其實對作用域的理解是理解整個javascript語言的關鍵所在,特別我在寫javascript筆記時候曾對很多怪異的javascript用法無法理解的透徹,究其原因還是沒有真正理解javascript裡作用域的概念。
Javascript裡的作用域到底決定瞭什麼呢?作用域決定瞭那些變量能被函數所訪問(註意:作用域是函數的內部屬性,談到作用域是繞不開的function),作用域也確定瞭this指針的指向。上篇博文裡我說道,程序其實就是不斷檢索數據的過程,那麼檢索數據的效率決定瞭程序的性能,因此作用域既然決定那些變量能被訪問,當然也決定瞭檢索這些變量的方式,所以想寫出高效的javascript程序靈活運用作用域的原理是關鍵瞭。
上篇博文裡面,我寫瞭一個函數,代碼如下:
<script type="text/javascript">
function add(a,b)
{
var sum = a + b;
return sum;
}
</script>
大傢可以看到這個函數屬於window而非function,在頁面被加載時候,add函數會被初始化,生成屬於自己的作用域鏈,上篇文章裡我在firebug裡設定斷點調試貼出瞭一個我截圖下來的變量圖,認為這就是函數add的Scope所包含的變量,現在發現當時理解是錯誤的,那些變量是window的“全局環境”的變量圖而非是add函數的內部屬性Scope的變量圖。在此我糾正一下。
下面我要著重講講作用域鏈和執行環境,這塊知識我在javascript對象創建的中篇裡提到過,這裡我將那些知識回顧下:
什麼是執行環境呢?在javascript裡面執行環境分為兩類,一類是全局環境,一類是局部環境,整個頁面裡被共享的方法和屬性就是在全局環境,相對於全局環境,函數{}號裡的執行環境就是局部環境,執行環境定義瞭變量或函數有權訪問的其他數據,決定瞭它們各自的行為,每個執行環境都定義瞭一個與之相關的變量對象,環境中定義的所有變量和函數都保存在這個對象裡,雖然我們自己編寫的代碼無法訪問這個對象,但解析器在處理數據時候後臺會使用到它。
全局執行環境另一種說法是最外圍的一個執行環境,在web瀏覽器的范圍中(actionscript也是施行瞭ECMAScript標準,它的全局范圍就和javascript的全局范圍不同),全局執行環境被認為是window對象,因此全局變量和函數都是作為window對象的方法和屬性來創建的,全局執行環境知道應用程序退出比如關閉網頁或瀏覽器才會被銷毀。而局部環境則是以函數對象作為關聯對象。
大傢要註意環境的前綴是:執行,也就說隻有函數被執行(局部執行環境)和頁面被加載(全局執行環境),下面就是頁面被加載時候的全局執行環境變量:
現在我們要執行add函數瞭,代碼如下:
function add(a,b)
{
var sum = a + b;
return sum;
}
console.log(add(10,20));
這是add函數的執行環境的變量圖是:
1.firebug:
2.chrome的代碼調試器:
代碼的結構圖如下:
在函數被執行時候,函數會創建一個“運行期上下文(execution context)”的內部對象,這個運行期上下文在我的理解裡就是函數的執行環境,每個運行期上下文都有自己的作用域鏈,用於標識符解析。
當運行期上下文被創建時候,他的作用域鏈初始化為當前運行函數【Scope】屬性中所含的對象(見firebug和chrome調試器裡的內存圖,比如add函數除瞭a,b,sum,this還有arguments,不過這個在firebug和chrome調試器裡看不到的)。這些值按照它們出現在函數中的順序,被復制到執行期上下文的作用域鏈中。這個過程一旦完成,一個被稱為“活動對象(activation object)”的新對象就為執行期上下文創建好瞭,活動對象作為函數運行期的可變對象,包含瞭所有局部變量,命名參數,參數集合以及this。然後此對象被推入到作用域鏈的前端。當運行期上下文被銷毀的時候,活動對象也隨之被銷毀。在函數執行過程中,每遇到一個變量都會進行一次標識符的解析,這種解析決定瞭我們從啥地方獲取變量或者將變量存儲到什麼地方,這個過程是搜索整個函數運行期上下文的作用域鏈,查找同名的標識符,搜索過程都是從作用域頭部開始,也就是當前運行函數的活動對象。找到瞭就使用它,沒有找到則會繼續搜索作用域鏈中的下一個對象,搜索過程會延續到找到標識符為止或者是沒有找到為止,這種情況就是標識符沒有被定義瞭。
在javascript裡使用變量就是在做標識符解析,由上面的解釋我們知道標識符的解析一定是有計算機性能的消耗的,當標識符位於執行上下文作用域鏈的位置越深,性能也就越慢瞭,那麼到底什麼地方最慢瞭?比如我們舉例的函數add,當我們搜索到global全局變量總會比函數內部的局部變量慢,假如我們定義個更復雜的函數裡面有多層嵌套的話,訪問全局變量就是性能的夢魘瞭。因此在函數中我們盡量多使用函數內部的局部變量。(由於瀏覽器的產品的差異,個別瀏覽器的訪問變量的性能可能會有差異,但是總體而言,訪問函數局部變量永遠是最快的)。
盡量使用局部變量,就帶來一個十分重要的提高程序性能的用法:我們在函數內部使用全局變量可以說是一種跨作用域操作,如果某個跨作用域的值在函數的內部被使用到一次以上,那麼我們就把它存儲到局部變量裡。
代碼書寫的格式就是:
function ftn()
{
var doc = document;
…….
}
將全局的變量用var定義到局部變量裡。
這個用法在jQuery源碼裡一開頭就清晰可以到看:
var jQuery = function( selector, context ) {
// The jQuery object is actually just the init constructor 'enhanced'
return new jQuery.fn.init( selector, context );
},
// Map over jQuery in case of overwrite
_jQuery = window.jQuery,
// Map over the $ in case of overwrite
_$ = window.$,
// Use the correct document accordingly with window argument (sandbox)
document = window.document,
jQuery把經常使用的全局變量都存儲在函數的局部變量裡。
摘自 夏天的森林