函數作用域和作用域鏈


作用域

所謂作用域就是:變量在聲明它們的函數體以及這個函數體嵌套的任意函數體內都是有定義的。

function scope(){
    var foo = "global";
    if(window.getComputedStyle){
        var a = "I'm if";
        console.log("if:"+foo); //if:global
    }
    while(1){
        var b = "I'm while";
        console.log("while:"+foo);//while:global
        break;
    }
    !function (){
        var c = "I'm function";
        console.log("function:"+foo);//function:global
    }();
    console.log(
         foo,//global
         a, // I'm if
         b, // I'm while
         c  // c is not defined
    );
}
scope();
  • scope函數中定義的foo變量,除過自身可以訪問以外,還可以在if語句、while語句和內嵌的匿名函數中訪問。 因此,foo的作用域就是scope函數體。
  • 在javascript中,if、while、for 等代碼塊不能形成獨立的作用域。因此,javascript中沒有塊級作用域,只有函數作用域。

 但是,在JS中有一種特殊情況:

 如果一個變量沒有使用var聲明,window便擁有了該屬性,因此這個變量的作用域不屬於某一個函數體,而是window對象。

function varscope(){
    foo = "I'm in function";
    console.log(foo);//I'm in function
}
varscope();
console.log(window.foo); //I'm in function

作用域鏈

 所謂作用域鏈就是:一個函數體中嵌套了多層函數體,並在不同的函數體中定義了同一變量, 當其中一個函數訪問這個變量時,便會形成一條作用域鏈。

foo = "window";
function first(){
    var foo = "first";
    function second(){
       var foo = "second";
       console.log(foo);
    }
    function third(){
       console.log(foo);
    }
    second(); //second
    third();  //first
}
first();

當執行second時,JS引擎會將second的作用域放置鏈表的頭部,其次是first的作用域,最后是window對象,於是會形成如下作用域鏈:

second->first->window,  此時,JS引擎沿着該作用域鏈查找變量foo, 查到的是 second

當執行third時,third形成的作用域鏈:third->first->window, 因此查到的是:frist

作用域鏈延長(with/catch)

with 和 catch 語句主要用來臨時擴展作用域鏈, 將語句中傳遞的變量對象添加到作用域的頭部。語句結束后,原作用域鏈恢復正常。

//with語句
foo = "window";
function first(){
    var foo = "first";
    function second(){
       var foo = "second";
       console.log(foo);
    }
    function third(obj){
       console.log(foo); //first
       with (obj){
           console.log(foo); //obj
       }
       console.log(foo); //first
    }
    var obj = {foo:'obj'};
    third(obj);
}
first();

//catch語句
var e = new Error('a');
try {
    throw new Error('b');
} catch (e) {
    console.log(e.message); //b
} 

在執行third()時,傳遞了一個obj對象,obj 中有屬性foo, 在執行with語句時,JS引擎將obj放置在了原鏈表的頭部,於是形成的作用域鏈如下:

obj->third->first->window, 此時查找到的foo就是obj中的foo,因此輸出的是 obj

而在with之前和之后,都是沿着原來的鏈表進行查找,從而說明在with語句結束后,作用域鏈已恢復正常。

this 關鍵字

在一個函數中,this總是指向當前函數的所有者對象,this總是在運行時才能確定其具體的指向, 也才能知道它的調用對象。

window.name = "window";
function f(){
    console.log(this.name);
}
f();//window

var obj = {name:'obj'};
f.call(obj); //obj

在執行f()時,此時f()的調用者是window對象,因此輸出 window 

f.call(obj) 是把f()放在obj對象上執行,相當於obj.f(),此時f 中的this就是obj,所以輸出的是 obj

實戰應用

demo1:

var foo = "window";
var obj = {
    foo : "obj",
    getFoo : function() {
        return function() {
            return this.foo;
        };
    }
};
var f = obj.getFoo();
f(); //window

demo2:

var foo = "window";
var obj = {
    foo : "obj",
    getFoo : function() {
        var that = this;
        return function(){
            return that.foo;
        };
    }
};
var f = obj.getFoo();
f(); //obj

代碼解析

// demo1:
//執行var  f = obj.getFoo()返回的是一個匿名函數,相當於:
var f = function(){
     return this.foo;
}
// f() 相當於window.f(), 因此f中的this指向的是window對象,this.foo相當於window.foo, 所以f()返回"window" 

// demo2:
// 執行var f = obj.getFoo() 同樣返回匿名函數,即:
var f = function(){
     return that.foo;
}
// 唯一不同的是f中的this變成了that, 要知道that是哪個對象之前,先確定f的作用域鏈:f->getFoo->window 並在該鏈條上查找that,
// 此時可以發現that指代的是getFoo中的this, getFoo中的this指向其運行時的調用者,
// 從var f = obj.getFoo() 可知此時this指向的是obj對象,因此that.foo 就相當於obj.foo,所以f()返回 "obj"

如對結果有疑惑,歡迎討論!

 

原創發布 @一像素  2015.12


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM