DIV CSS 佈局教程網

 DIV+CSS佈局教程網 >> 網頁腳本 >> JavaScript入門知識 >> JavaScript基礎知識 >> javascript作用域鏈學習筆記
javascript作用域鏈學習筆記
編輯:JavaScript基礎知識     

我的博客地址:http://hovertree.com/

作用域鏈

  1. ”JavaScript中的函數運行在它們被定義的作用域裡,而不是它們被執行的作用域裡.” ——權威指南

  2. 在JavaScript中,一切皆對象,包括函數。函數對象和其它對象一樣,擁有可以通過代碼訪問的屬性和一系列僅供JavaScript引擎訪問的內部屬性。其中一個內部屬性是[[Scope]],由ECMA-262標准第三版定義,該內部屬性包含了函數被創建的作用域中對象的集合,這個集合被稱為函數的作用域鏈,它決定了哪些數據能被函數訪問。

  3. 在一個函數被定義的時候, 會將它定義時刻的scope chain鏈接到這個函數對象的[[scope]]屬性.

  4. 在一個函數對象被調用的時候,會創建一個活動對象(也就是一個對象), 該對象包含了函數的所有局部變量、命名參數、參數集合以及this,然後此對象會被推入作用域鏈的前端,當運行期上下文被銷毀,活動對象也隨之銷毀。

  5. 在每次調用一個函數的時候 ,就會進入一個函數內的作用域,當從函數返回以後,就返回調用前的作用域.

實例解析

var sayHello = function(l,s){
    var word = "hello world";
}

sayHello();
  • 在執行sayHello定義語句的時候, 會創建一個這個函數對象的[[scope]]屬性。

  • 將這個[[scope]]屬性, 鏈接到定義它的作用域鏈上。此時因為func定義在全局環境, 所以此時的[[scope]]只是指向全局活動對象window active object.

  • 在調用sayHello的時候, 會創建一個活動對象(假設為fObj),並創建arguments屬性。然後會給這個對象添加倆個命名屬性fObj.l, fObj.s; 對於每一個在這個函數中申明的局部變量和函數定義, 都作為該活動對象的同名命名屬性。對於局部變量,變量的值會在真正執行的時候才計算, 此時只是簡單的賦為undefined.

  • 將調用參數賦值給形參,對於缺少的調用參數,賦值為undefined。

  • 將這個活動對象做為scope chain的最前端, 並將func的[[scope]]屬性所指向的,定義sayHello時候的頂級活動對象, 加入到scope chain.

  • 在發生標識符解析的時候, 就會逆向查詢當前scope chain列表的每一個活動對象的屬性,如果找到同名的就返回。找不到,那就是這個標識符沒有被定義。

作用域鏈全過程解析:

function factory() {
     var name = 'laruence';
     var intro = function(){
          alert('I am ' + name);
     }
     return intro;
}

function app(para){
     var name = para;
     var func = factory();
     func();
}

app('eve');

首先當調用app的時候, scope chain是由: {window活動對象(全局)}->{app的活動對象} 組成。此時的scope chain如下:(對於局部變量,變量的值會在真正執行的時候才計算, 此時只是簡單的賦為undefined.

[[scope chain]] = [
{
     para : 'eve',
     name : undefined,
     func : undefined,
     arguments : []
}, {
     window call object
}
]

當調用進入factory的函數體的時候, 此時的factory的scope chain為:

[[scope chain]] = [
{
     name : undefined,
     intor : undefined
}, {
     window call object
}
]

注意: 此時的作用域鏈中, 並不包含app的活動對象.(JavaScript中的函數運行在它們被定義的作用域裡,而不是它們被執行的作用域裡.)

在定義intro函數的時候, intro函數的[[scope]]為:

[[scope chain]] = [
{
     name : 'laruence',
     intor : undefined
}, {
     window call object
}
]

從factory函數返回以後,在app體內調用intor的時候, 發生了標識符解析, 而此時的sope chain是:

[[scope chain]] = [
{
     intro call object
}, {
     name : 'laruence',
     intor : undefined
}, {
     window call object
}
]

所以, name標識符解析的結果(在上面的作用域鏈中一層層往上匹配)應該是factory活動對象中的name屬性, 也就是’laruence’。

標識符解析過程:
該過程從作用域鏈頭部,也就是從活動對象開始搜索,查找同名的標識符,如果找到了就使用這個標識符對應的變量,如果沒找到繼續搜索作用域鏈中的下一個對象,如果搜索完所有對象都未找到,則認為該標識符未定義。函數執行過程中,每個標識符都要經歷這樣的搜索過程。

利用作用域鏈的代碼優化

  1. 把全局變量存儲到局部變量裡再使用

從作用域鏈的結構可以看出,在運行期上下文的作用域鏈中,標識符所在的位置越深,讀寫速度就會越慢。全局變量總是存在於運行期上下文作用域鏈的最末端,因此在標識符解析的時候,查找全局變量是最慢的。所以,在編寫代碼的時候應盡量少使用全局變量,盡可能使用局部變量。一個好的經驗法則是:如果一個跨作用域的對象被引用了一次以上,則先把它存儲到局部變量裡再使用。例如下面的代碼:

function changeColor(){
    var doc=document;
    doc.getElementById("btnChange").onclick=function(){
        doc.getElementById("targetCanvas").style.backgroundColor="red";
    };
}
  1. 避免改變作用域鏈
    函數每次執行時對應的運行期上下文都是獨一無二的,所以多次調用同一個函數就會導致創建多個運行期上下文,當函數執行完畢,執行上下文會被銷毀。每一個運行期上下文都和一個作用域鏈關聯。一般情況下,在運行期上下文運行的過程中,其作用域鏈只會被 with 語句和 catch 語句影響。
function initUI(){
    with(document){
        var bd=body,
            links=getElementsByTagName("a"),
            i=0,
            len=links.length;
        while(i < len){
            update(links[i++]);
        }
        getElementById("btnInit").onclick=function(){
            doSomething();
        };
    }
}

with 語句的作用是將代碼的作用域設置到一個特定的對象中

當代碼運行到with語句時,運行期上下文的作用域鏈臨時被改變了。一個新的可變對象被創建,它包含了參數指定的對象的所有屬性。這個對象將被推入作用域鏈的頭部,這意味著函數的所有局部變量現在處於第二個作用域鏈對象中,因此訪問代價更高了。

參考文章:Javascript作用域原理、理解 JavaScript 作用域和作用域鏈

XML學習教程| jQuery入門知識| AJAX入門| Dreamweaver教程| Fireworks入門知識| SEO技巧| SEO優化集錦|
Copyright © DIV+CSS佈局教程網 All Rights Reserved