javascript執(zhí)行上下文和調(diào)用棧
- javascript在執(zhí)行過程中通常在以下環(huán)境
- global code: 全局作用域
- Function code: 當(dāng)代碼執(zhí)行進入到函數(shù)體當(dāng)中裹匙。
- Eval code: 在 eval 函數(shù)內(nèi)部執(zhí)行的文本。
var name = 'Bill'
function print() {
console.log(name) //Bill
// console.log(age) //ReferenceError: age is not defined
function say() {
var age = 3 //age是在函數(shù)say內(nèi),而say在print的函數(shù)作用域內(nèi)
console.log(name) //Bill
}
say()
}
print()
name變量和print函數(shù)都在全局作用域內(nèi)
- 每個函數(shù)都會創(chuàng)造一個新的上下文,并且創(chuàng)建出一個局部的作用域
- 作用域聲明的東西不能被當(dāng)前函數(shù)作用外部訪問到
執(zhí)行上下文棧
- 代碼進入函數(shù)體內(nèi)部如果遇到函數(shù),會給這個函數(shù)重新創(chuàng)建一個新的執(zhí)行上下文,然后將它壓入棧中
- 瀏覽器永遠(yuǎn)會執(zhí)行當(dāng)前棧頂部的執(zhí)行上下文,一旦函數(shù)執(zhí)行完后览芳,就會從棧中彈出,然后執(zhí)行當(dāng)前棧中的下一個上下文鸿竖。
(function foo(i) {
debugger;
if (3 === i) {
return
} else {
foo(++i)
}
})(0)
//入棧
// foo(3) i = 3
// foo(2) i = 2
// foo(1) i = 1
// foo(0) i = 0
// global
出棧
從上往下走沧竟,大家把代碼復(fù)制到瀏覽器的debug里去看看堆棧,單步調(diào)試缚忧,就能看到結(jié)果
- 解釋器執(zhí)行代碼的順序
- 尋找調(diào)用函數(shù)的代碼
- 在執(zhí)行 函數(shù) 代碼之前, 創(chuàng)建 執(zhí)行上下文.
- 進入創(chuàng)建階段:
- 初始化 作用域鏈.
- 創(chuàng)建變量對象:
- 創(chuàng)建 參數(shù)對象, 檢查參數(shù)的上下文, 初始化其名稱和值并創(chuàng)建一個引用拷貝悟泵。
- 掃描上下文中的函數(shù)聲明:
- 對于每個被發(fā)現(xiàn)的函數(shù), 在 變量對象 中創(chuàng)建一個和函數(shù)名同名的屬性,這是函數(shù)在內(nèi)存中的引用闪水。
- 如果函數(shù)名已經(jīng)存在, 引用值將會被覆蓋糕非。
- 掃描上下文中的變量聲明:
- 對于每個被發(fā)現(xiàn)的變量聲明,在變量對象中創(chuàng)建一個同名屬性并初始化值為 undefined。
- 如果變量名在 變量對象 中已經(jīng)存在, 什么都不做球榆,繼續(xù)掃描朽肥。
- 確定上下文中的 "this"
- 激活 / 代碼執(zhí)行階段:
- 執(zhí)行 / 在上下文中解釋函數(shù)代碼,并在代碼逐行執(zhí)行時給變量賦值持钉。
- 讓我們來看一個例子:
function foo(i) {
var a = 'hello';
var b = function privateB() {
};
function c() {
}
}
foo(22);
- 在調(diào)用foo(22) 的時候, 創(chuàng)建階段 看起來像是這樣:
fooExecutionContext = {
scopeChain: { ... },
variableObject: {
arguments: {
0: 22,
length: 1
},
i: 22,
c: pointer to function c()
a: undefined,
b: undefined
},
this: { ... }
}
你可以發(fā)現(xiàn), 創(chuàng)建階段 掌管著屬性名的定義衡招,而不是給它們賦值,不過參數(shù)除外每强。 一旦 創(chuàng)建階段 完成之后始腾,執(zhí)行流就會進入函數(shù)中。 在函數(shù)執(zhí)行完之后空执,激活 / 代碼 執(zhí)行階段 看起來像是這樣:
fooExecutionContext = {
scopeChain: { ... },
variableObject: {
arguments: {
0: 22,
length: 1
},
i: 22,
c: pointer to function c()
a: 'hello',
b: pointer to function privateB()
},
this: { ... }
}
作用域
function one() {
two();
function two() {
three();
function three() {
alert('I am at function three');
}
}
}
one();
入棧
- three()
- two()
- one()
- global
- three作用域 = three + two + one + global
var myConsole = [];
for (var i = 0; i < 5; i++) {
myConsole.push(
function inner() {
console.log(i)
}
);
}
console.log(i) // 5
myConsole[0]() //5
myConsole[1]() //5
myConsole[2]() //5
myConsole[3]() //5
myConsole[4]() //5
- 這個inner是創(chuàng)建在glbal上的浪箭,那么他的作用域也是global
- for語句執(zhí)行完之后i再全局作用域上已經(jīng)是5了。
閉包的作用域
function foo() {
var a = 'private variable';
return function bar() {
alert(a);
}
}
var callAlert = foo();
callAlert(); // private variable
//入棧
// bar()
// foo()
// global
//bar的作用域是bar + foo + global
- 我們通過callAlert()獲得了foo辨绊,而foo函數(shù)返回了bar的函數(shù)指針奶栖,
- 那么callAlert的作用域和bar一樣了,所以我們就能訪問到foo函數(shù)內(nèi)的私有變量了