javascript做用域鏈學習筆記

做用域鏈

  1. 」JavaScript中的函數運行在它們被定義的做用域裏,而不是它們被執行的做用域裏.」 ——權威指南javascript

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

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

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

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

實例解析

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

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

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

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

  • 將調用參數賦值給形參,對於缺乏的調用參數,賦值爲undefined。優化

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

  • 在發生標識符解析的時候, 就會逆向查詢當前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 做用域和做用域鏈

個人博客地址:http://bigdots.github.iohttp://www.cnblogs.com/yzg1/

相關文章
相關標籤/搜索