執(zhí)行環(huán)境
執(zhí)行環(huán)境
:是代碼執(zhí)行時(shí)產(chǎn)生的一個(gè)上下文環(huán)境(context)腿箩。每個(gè)執(zhí)行環(huán)境都包含一個(gè)變量對(duì)象
, 用來保存該執(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ù)拒担。
作用域
一般的編程語言如c++和java嘹屯,都有塊作用域({}大括號(hào)形成一個(gè)塊作用域)。而javascript語言在es6標(biāo)準(zhǔn)之前从撼,卻只有全局作用域和函數(shù)作用域州弟,沒有塊作用域。比如:
function foo() {
{
var a = 'hello';
}
console.log(a); // 打印 hello
}
foo();
由于沒有塊作用域低零,a變量能被外層調(diào)用到婆翔。
作用域鏈
作用域鏈
, 執(zhí)行環(huán)境在初始化時(shí)會(huì)創(chuàng)建作用域鏈,用來標(biāo)記當(dāng)前作用域內(nèi)成員的訪問次序掏婶。它本質(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ǔ)枝秤。
下面的例子用來說明作用域鏈的工作原理
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)沒查找到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)部也沒有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ì)象,也沒有找到c變量叔遂,查找失敗他炊,打印c沒有定義
的錯(cuò)誤。
塊作用域
es6引入了let關(guān)鍵字和塊作用域已艰。
依然是上面的例子痊末,將var改成let,let修飾的a變量哩掺,只存在于自己的塊作用域中凿叠,不會(huì)被外層訪問。
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
}
本來想要得到的結(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。這種問題肤晓,在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)潔,還更加好理解了呢岩瘦?