作用域與作用域鏈

1、作用域

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

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();

(1)scope函數中定義的foo變量,除過自身可以訪問以外,還可以在if語句、while語句和內嵌的匿名函數中訪問。 因此,foo的作用域就是scope函數體。

(2)在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

2、作用域鏈

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

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

3、作用域鏈延長(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語句結束後,作用域鏈已恢復正常。

4、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

5、實戰應用

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

f(); //window

實戰2:

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

6、代碼解析

//執行var f = obj.getFoo()返回的是一個匿名函數,相當於:
示例1:
var f = function(){
return this.foo;
}

//f() 相當於window.f(), 因此f中的this指向的是window對象,this.foo相當於window.foo, 所以f()返回"window"

示例2:

//執行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"

歡迎各位想學習前端開發的小夥伴加入藍軌跡WEB開發交流羣 143046757 一同探討~
點擊鏈接加入羣聊 https://jq.qq.com/?_wv=1027&k=5urtQKW

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章