2025-02-17

 

上篇文章裡我結束瞭對象的創建的內容,最後引出瞭作用域鏈和執行環境的問題。當我對這塊知識有瞭更深入的瞭解後,回頭看看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把經常使用的全局變量都存儲在函數的局部變量裡。

  

摘自 夏天的森林

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *