作用域鏈
作用域:提高了程序邏輯的局部性,增強程序的可靠性,減少名字沖突。
看一個例子:
var s1 = 'sharpxiajun';
function ftn(){
var s2 = 'xtq';
console.log(this); //window
console.log('s1:' + this.s1 + ';s2:' + this.s2); // s1:sharpxiajun;s2:undefined
console.log('s1:'+this.s1+';s2:' + s2); //s1:sharpxiajun;s2:xtq
}
ftn();
在javascript世界里有一個大的作用域環(huán)境,這個環(huán)境就是window,window環(huán)境不需要我們自己使用什么方式構(gòu)建,頁面加載時候頁面會自動構(gòu)造的,上面代碼里有一個大括號,這個大括號是對函數(shù)的定義,運行時,我們發(fā)現(xiàn)函數(shù)作用域內(nèi)部定義的s2變量是不能被window對象訪問的,因此s2變量是被{}保護起來了,它的生命周期和這個函數(shù)的生命周期有關(guān)。
在javascript里作用域有一個專門的定義execution context,叫執(zhí)行上下文或執(zhí)行環(huán)境,我們來想想javascript里那些情況是執(zhí)行:
情況一:當(dāng)頁面加載時候在script標(biāo)簽下的javascript代碼會按順序執(zhí)行,而這些能被執(zhí)行的代碼都是屬于window的變量或函數(shù);
情況二:當(dāng)函數(shù)的名字后面加上小括號(),例如ftn(),這也是在執(zhí)行,不過它執(zhí)行的是函數(shù)。
如此說來,javascript里的執(zhí)行環(huán)境有兩類一類是全局執(zhí)行環(huán)境,即window代表的全局環(huán)境,一類是函數(shù)代表的函數(shù)執(zhí)行環(huán)境,這也就是我們常說的局部作用域。
執(zhí)行環(huán)境在javascript語言里并非是一個抽象的概念,而是有具體的實現(xiàn),這個實現(xiàn)其實是個對象,這個對象也有個名字叫做variable object,稱為上下文變量, 上下文變量存儲的是上下文變量所處執(zhí)行環(huán)境里定義的所有的變量和函數(shù)。
全局執(zhí)行環(huán)境的上下文變量是可以訪問到的,它就是window對象,所以我們說window能代表全局作用域是有道理的,但是局部作用域即函數(shù)的執(zhí)行環(huán)境里的上下文變量是代碼不能訪問到的,不過javascript引擎在處理數(shù)據(jù)時候會使用到它。
在javascript語言里還有一個概念,它的名字叫做execution context stack,就是執(zhí)行環(huán)境棧,每個要被執(zhí)行的函數(shù)都會先把函數(shù)的執(zhí)行環(huán)境壓入到執(zhí)行環(huán)境棧里,函數(shù)執(zhí)行完畢后,這個函數(shù)的執(zhí)行環(huán)境就會被執(zhí)行環(huán)境棧彈出,例如上面的例子:函數(shù)執(zhí)行時候函數(shù)的執(zhí)行環(huán)境會被壓入到執(zhí)行環(huán)境棧里,函數(shù)執(zhí)行完畢,執(zhí)行環(huán)境棧會把這個環(huán)境彈出,執(zhí)行環(huán)境棧的控制權(quán)就會交由全局環(huán)境,如果函數(shù)后面還有代碼,那么代碼就是接著執(zhí)行。如果函數(shù)里嵌套了函數(shù),那么嵌套函數(shù)執(zhí)行完畢后,執(zhí)行環(huán)境棧的控制權(quán)就交由了外部函數(shù),然后依次類推,最后就是全局執(zhí)行環(huán)境了。
講到這里我們大名鼎鼎的作用域鏈要登場了,函數(shù)的執(zhí)行環(huán)境被壓入到執(zhí)行環(huán)境棧里后,函數(shù)就要執(zhí)行了,函數(shù)執(zhí)行的第一步不是執(zhí)行函數(shù)里的第一行代碼而是在上下文變量里構(gòu)造一個作用域鏈,作用域鏈的英文名字叫做scope chain,作用域鏈的作用是保證執(zhí)行環(huán)境里有權(quán)訪問的變量和函數(shù)是有序的,這個概念里有兩個關(guān)鍵意思:有權(quán)訪問和有序,我們看看下面的代碼:
var b1 = 'b1';
function ftn1(){
var b2 = 'b2';
var b1 = 'bbb';
function ftn2(){
var b3 = 'b3';
b2 = b1;
b1 = b3;
console.log('b1:'+ b1 +';b2:' + b2 +';b3' + b3);
// b1:b3; b2:bbb; b3:b3
}
ftn2();
}
ftn1();
console.log(b1);
這個例子我們發(fā)現(xiàn),ftn2函數(shù)可以訪問變量b1,b2,這個體現(xiàn)了有權(quán)訪問的概念,當(dāng)ftn1作用域里改變了b1的值并且把b1變量重新定義為ftn1的局部變量,那么ftn2訪問到的b1就是ftn1的,ftn2訪問到b1后就不會在全局作用域里查找b1了,這個體現(xiàn)了有序性。
廣大程序員對作用域鏈的理解有兩塊一塊是作用域,而作用域在javascript語言里指的是執(zhí)行環(huán)境execution context,執(zhí)行環(huán)境在javascript引擎里是通過上下文變量體現(xiàn)的variable object,javascript引擎里還有一個概念就是執(zhí)行環(huán)境棧execution context stack,當(dāng)某一個函數(shù)的執(zhí)行環(huán)境壓入到了執(zhí)行環(huán)境棧里,這個時候就會在上下文變量里構(gòu)造一個對象,這個對象就是作用域鏈scope chain,而這個作用域鏈就是廣大程序員理解的第二塊知識,作用域鏈的作用是保證執(zhí)行環(huán)境里有權(quán)訪問的變量和函數(shù)是有序的,作用域鏈的變量只能向上訪問,變量訪問到window對象即被終止,作用域鏈向下訪問變量是不被允許的。
很多人常常認為作用域鏈是理解this指針的關(guān)鍵,這個理解是不正確的,this指針構(gòu)造是和作用域鏈同時發(fā)生的,也就是說在上下文變量構(gòu)造作用域鏈的同時還會構(gòu)造出一個this對象,this對象是屬于上下文變量,而this變量的值就是當(dāng)前執(zhí)行環(huán)境外部的上下文變量的一份拷貝,這份拷貝里沒有作用域鏈變量的
function show () {
alert(this);
function show2 () {
alert(this);
}
show2();
}
show();
我們看到函數(shù)show 和 show2里的shis指針都是指向window,這是為什么了?因為在JavaScript我們定義函數(shù)方式是通過function xxx() {}形式,那么這個函數(shù)不管定義在哪里(包括匿名函數(shù)),它都是屬于全局對象window,所以他們的執(zhí)行環(huán)境的外部的執(zhí)行上下文都是指向window。
var a = 10;
function test(){
a = 100;
alert(a);
alert(this.a);
var a;
alert(a);
}
test();
//正確答案:100 10 100
var a = 100;
function test(){
alert(a);
var a = 10;
alert(a);
}
test();
//正確答案: undefined 10
var a = 100;
function test(){
alert(a);
a = 10;
alert(a);
}
test();
alert(a);
//正確答案:100 10 10;