Javascript基礎(chǔ)--執(zhí)行環(huán)境和作用域鏈

執(zhí)行環(huán)境

執(zhí)行環(huán)境:是代碼執(zhí)行時(shí)產(chǎn)生的一個(gè)上下文環(huán)境(context)。每個(gè)執(zhí)行環(huán)境都包含一個(gè)變量對(duì)象, 用來(lái)保存該執(zhí)行環(huán)境下的變量和函數(shù)。執(zhí)行環(huán)境包括全局執(zhí)行環(huán)境和函數(shù)執(zhí)行環(huán)境。
全局執(zhí)行環(huán)境:是javascript程序代碼一開始執(zhí)行時(shí)就產(chǎn)生的上下文環(huán)境。瀏覽器中的全局執(zhí)行環(huán)境就是window對(duì)象----全局變量和全局函數(shù),即是window內(nèi)的屬性和方法。
函數(shù)執(zhí)行環(huán)境:當(dāng)函數(shù)代碼執(zhí)行時(shí),會(huì)產(chǎn)生的函數(shù)執(zhí)行環(huán)境。該執(zhí)行環(huán)境下的變量對(duì)象保存函數(shù)的內(nèi)部變量和內(nèi)部函數(shù)。

作用域

一般的編程語(yǔ)言如c++和java,都有塊作用域({}大括號(hào)形成一個(gè)塊作用域)。而javascript語(yǔ)言在es6標(biāo)準(zhǔn)之前,卻只有全局作用域和函數(shù)作用域,沒(méi)有塊作用域。比如:

function foo() {
  {
     var a = 'hello';
  }
  console.log(a); // 打印 hello
}
foo();

由于沒(méi)有塊作用域,a變量能被外層調(diào)用到。

作用域鏈

作用域鏈, 執(zhí)行環(huán)境在初始化時(shí)會(huì)創(chuàng)建作用域鏈,用來(lái)標(biāo)記當(dāng)前作用域內(nèi)成員的訪問(wèn)次序。它本質(zhì)是一個(gè)指針列表,最前端節(jié)點(diǎn)稱為活動(dòng)對(duì)象,等于當(dāng)前執(zhí)行環(huán)境的變量對(duì)象。第二個(gè)節(jié)點(diǎn)是外層函數(shù)的變量對(duì)象,第三個(gè)節(jié)點(diǎn)是外層函數(shù)的外層函數(shù)的節(jié)點(diǎn)...... 直到最后一個(gè)節(jié)點(diǎn),即全局執(zhí)行環(huán)境的變量對(duì)象。作用域鏈,是一個(gè)很重要的概念,它是實(shí)現(xiàn)閉包的一個(gè)理論基礎(chǔ)。
下面的例子用來(lái)說(shuō)明作用域鏈的工作原理

var a = 'hello';
var b = 'world';

function foo() {
    var a = 'hi';
    var bar = function() {
        console.log(a); // 打印 hi
        console.log(b); // 打印 world   
        console.log(c); // 報(bào)錯(cuò): c is not defined
    }
    return bar;
}

foo()();

當(dāng)執(zhí)行foo()時(shí),會(huì)創(chuàng)建函數(shù)foo的作用域鏈,頭結(jié)點(diǎn)即當(dāng)前執(zhí)行環(huán)境的變量對(duì)象,包含變量a(值為hi)和內(nèi)部函數(shù)bar。
當(dāng)執(zhí)行foo()()時(shí),即執(zhí)行foo函數(shù)的內(nèi)部函數(shù)bar時(shí),也創(chuàng)建作用域鏈,。當(dāng)執(zhí)行console.log(a)時(shí),由于當(dāng)前活動(dòng)對(duì)象內(nèi)沒(méi)查找到a變量,就向作用域鏈的前一個(gè)節(jié)點(diǎn)查找,即函數(shù)foo的變量對(duì)象,發(fā)現(xiàn)存在a變量,查找結(jié)束,打印出a變量的值。
當(dāng)執(zhí)行console.log(b)時(shí),發(fā)現(xiàn)函數(shù)foo的執(zhí)行環(huán)境的變量對(duì)象內(nèi)部也沒(méi)有b變量,繼續(xù)像前一個(gè)節(jié)點(diǎn)查找,即全局執(zhí)行環(huán)境的變量對(duì)象,發(fā)現(xiàn)內(nèi)部包含b變量(即全局變量),查找結(jié)束,打印全局變量b的值。
當(dāng)執(zhí)行console.log(c)時(shí),一直查到到最外層的全局變量對(duì)象,也沒(méi)有找到c變量,查找失敗,打印c沒(méi)有定義的錯(cuò)誤。

塊作用域

es6引入了let關(guān)鍵字和塊作用域。
依然是上面的例子,將var改成let,let修飾的a變量,只存在于自己的塊作用域中,不會(huì)被外層訪問(wèn)。

function foo() {
  {
     let a = 'hello';
  }
  console.log(a); // 報(bào)錯(cuò):"a is not defined"
}
foo();

下面的代碼可能在實(shí)際開發(fā)中遇到:

function arrayFun() {
    var arr = [];
    for (var i = 0; i < 5; i++) {
        arr.push(function () {
            console.log(i);
        });
    }
    return arr;
}
var retArr = arrayFun();
for (var i = 0; i < 5; i++) {
    retArr[i]();   // 打印 5,5,5,5,5
}

本來(lái)想要得到的結(jié)果是0,1,2,3,4。但由于var修飾的變量i存在于函數(shù)作用域arrayFun中,當(dāng)執(zhí)行完arrayFun,i的值變?yōu)?,之后在調(diào)用數(shù)組arr內(nèi)的函數(shù)成員時(shí),打印值都是5。這種問(wèn)題,在es6之前,通常的解決方案是將i作為參數(shù)傳遞給匿名函數(shù):

function arrayFun() {
    var arr = [];
    for (var i = 0; i < 5; i++) {
        arr.push(function (j) {
            return function() {
                console.log(j);
            }
        }(i));
    }
    return arr;
}
var retArray = arrayFun();
for (var i = 0; i < 5; i++) {
    retArray[i]();   // 打印 0, 1, 2, 3, 4
}

在es6中,使用let修飾變量i,將使得i存在于快作用域下----for循環(huán)的大括號(hào):{}.

function arrayFun() {
    var arr = [];
    for (let i = 0; i < 5; i++) {
        arr.push(function () {
            console.log(i);
        });
    }
    return arr;
}
var retArr = arrayFun();
for (let i = 0; i < 5; i++) {
    retArr[i]();   // 打印 0,1,2,3,4
}

看,是不是比之前的代碼簡(jiǎn)潔,還更加好理解了呢?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容