js運(yùn)行步驟預(yù)編譯【全局】: 【函數(shù)】: 創(chuàng)建 AO( Activation Object ) 對(duì)象(執(zhí)行上下文); 找形參和變量聲明,將形參和變量名作為 AO 對(duì)象的屬性名,值為 undefined(有重復(fù)的名稱只寫一個(gè)即可); 將形參與實(shí)參值統(tǒng)一(用實(shí)參的值替換 undefined); 在函數(shù)體中找函數(shù)聲明,將函數(shù)名添加到 AO 對(duì)象的屬性中,值為函數(shù)體(如屬性名重復(fù),則覆蓋前面的)。
【執(zhí)行上下文/執(zhí)行環(huán)境】組成: 變量對(duì)象(Variable object,VO) :包含變量的對(duì)象,無法訪問。 作用域鏈(Scope chain):作用域即變量對(duì)象,作用域鏈?zhǔn)且粋€(gè)由變量對(duì)象組成的帶頭結(jié)點(diǎn)的單向鏈表,其主要作用就是用來進(jìn)行變量查找;而[[Scope]]屬性是一個(gè)指向這個(gè)鏈表頭節(jié)點(diǎn)的指針 this:指向一個(gè)環(huán)境對(duì)象
【參考】https://www.jianshu.com/p/76ed896bbf91 執(zhí)行環(huán)境(執(zhí)行上下文 execution context)定義有時(shí)也稱環(huán)境,執(zhí)行環(huán)境定義了變量或函數(shù)有權(quán)訪問的其他數(shù)據(jù) ,決定了它們各自的行為。而每個(gè)執(zhí)行環(huán)境都有一個(gè)與之相關(guān)的變量對(duì)象,環(huán)境中定義的所有變量和函數(shù)都保存在這個(gè)對(duì)象中。 執(zhí)行過程當(dāng)JavaScript解釋器初始化執(zhí)行代碼時(shí),它首先默認(rèn)進(jìn)入全局執(zhí)行環(huán)境,從此刻開始,函數(shù)的每次調(diào)用都會(huì)創(chuàng)建一個(gè)新的執(zhí)行環(huán)境。 當(dāng)javascript代碼被瀏覽器載入后,默認(rèn)最先進(jìn)入的是一個(gè)全局執(zhí)行環(huán)境。 當(dāng)在全局執(zhí)行環(huán)境中調(diào)用執(zhí)行一個(gè)函數(shù)時(shí),程序流就進(jìn)入該被調(diào)用函數(shù)內(nèi),此時(shí)JS引擎就會(huì)為該函數(shù)創(chuàng)建一個(gè)新的執(zhí)行環(huán)境,并且將其壓入到執(zhí)行環(huán)境堆棧的頂部。瀏覽器總是執(zhí)行當(dāng)前在堆棧頂部的執(zhí)行環(huán)境,一旦執(zhí)行完畢,該執(zhí)行環(huán)境就會(huì)從堆棧頂部被彈出,然后,進(jìn)入其下的執(zhí)行環(huán)境執(zhí)行代碼。這樣,堆棧中的執(zhí)行環(huán)境就會(huì)被依次執(zhí)行并且彈出堆棧,直到回到全局執(zhí)行環(huán)境。 執(zhí)行環(huán)境完成可以分為創(chuàng)建和執(zhí)行兩個(gè)階段。1、在創(chuàng)建階段,解析器首先會(huì)創(chuàng)建一個(gè)變量對(duì)象【variable object】(函數(shù)中稱為活動(dòng)對(duì)象【activation object】),它由定義在執(zhí)行環(huán)境中的變量、函數(shù)聲明、和參數(shù)組成。在這個(gè)階段,作用域鏈會(huì)被初始化,this的值也會(huì)被最終確定。 2、在執(zhí)行階段,代碼被解釋執(zhí)行。 具體過程:每次調(diào)用函數(shù),都會(huì)創(chuàng)建新的執(zhí)行上下文。在JavaScript解釋器內(nèi)部,每次調(diào)用執(zhí)行上下文,分為兩個(gè)階段: 2.1 創(chuàng)建階段【若是函數(shù),當(dāng)函數(shù)被調(diào)用,但未執(zhí)行任何其內(nèi)部代碼之前】 在進(jìn)入執(zhí)行上下文階段,只會(huì)將有 var,function修飾的變量或方法添加到變量對(duì)象中。 創(chuàng)建作用域鏈(Scope Chain) 創(chuàng)建變量對(duì)象(變量,函數(shù)和參數(shù)) 確定this的指向 2.2 激活/代碼執(zhí)行階段: 變量賦值 函數(shù)引用, 解釋/執(zhí)行其他代碼
變量對(duì)象 組成
函數(shù)的所有形參 (如果是函數(shù)上下文) 由名稱和對(duì)應(yīng)值組成的一個(gè)變量對(duì)象的屬性被創(chuàng)建 沒有實(shí)參,屬性值設(shè)為 undefined 函數(shù)聲明 由名稱和對(duì)應(yīng)值(函數(shù)對(duì)象(function-object))組成一個(gè)變量對(duì)象的屬性被創(chuàng)建 如果變量對(duì)象已經(jīng)存在相同名稱的屬性,則完全替換這個(gè)屬性 變量聲明 由名稱和對(duì)應(yīng)值(undefined)組成一個(gè)變量對(duì)象的屬性被創(chuàng)建; 如果變量名稱跟已經(jīng)聲明的形式參數(shù)或函數(shù)相同,則變量聲明不會(huì)干擾已經(jīng)存在的這類屬性
可以將每個(gè)執(zhí)行上下文抽象為一個(gè)對(duì)象并有三個(gè)屬性:
executionContextObj = {
scopeChain: { /* 變量對(duì)象(variableObject) 所有父執(zhí)行上下文的變量對(duì)象*/ },
variableObject: { /*函數(shù) arguments/參數(shù),內(nèi)部變量和函數(shù)聲明 */ },
this: {}
}
解釋器執(zhí)行代碼的偽邏輯(函數(shù))1、查找調(diào)用函數(shù)的代碼。
2、執(zhí)行函數(shù)代碼之前,先創(chuàng)建執(zhí)行上下文。
3、進(jìn)入創(chuàng)建階段:
3.1 初始化作用域鏈:
3.2 創(chuàng)建變量對(duì)象:
3.2.1 創(chuàng)建arguments對(duì)象,檢查上下文,初始化參數(shù)名稱和值并創(chuàng)建引用的復(fù)制。
3.2.2 掃描上下文的函數(shù)聲明:
為發(fā)現(xiàn)的每一個(gè)函數(shù),在變量對(duì)象上創(chuàng)建一個(gè)屬性——確切的說是函數(shù)的名字——其有一個(gè)指向函數(shù)在內(nèi)存中的引用。
如果函數(shù)的名字已經(jīng)存在,引用指針將被重寫。
3.2.3 掃描上下文的變量聲明:
為發(fā)現(xiàn)的每個(gè)變量聲明,在變量對(duì)象上創(chuàng)建一個(gè)屬性——就是變量的名字,并且將變量的值初始化為undefined
如果變量的名字已經(jīng)在變量對(duì)象里存在,將不會(huì)進(jìn)行任何操作并繼續(xù)掃描。
3.3 求出上下文內(nèi)部“this”的值。
4、激活/代碼執(zhí)行階段:
在當(dāng)前上下文上運(yùn)行/解釋函數(shù)代碼,并隨著代碼一行行執(zhí)行指派變量的值。
demo: function foo(i) {
var a = 'hello';
var b = function privateB() {
};
function c() {
}
}
foo(22);
1、創(chuàng)建階段:foo(22)函數(shù)調(diào)用時(shí) fooExecutionContext = {
scopeChain: { ... },
variableObject: {
arguments: {
0: 22,
length: 1
},
i: 22,
c: pointer to function c()
a: undefined,
b: undefined
},
this: { ... }
}
2、執(zhí)行階段:執(zhí)行流進(jìn)入函數(shù)并且激活/代碼執(zhí)行階段 fooExecutionContext = {
scopeChain: { ... },
variableObject: {
arguments: {
0: 22,
length: 1
},
i: 22,
c: pointer to function c()
a: 'hello',
b: pointer to function privateB()
},
this: { ... }
}
注意: 單線程 同步執(zhí)行 唯一的全局執(zhí)行環(huán)境 局部執(zhí)行環(huán)境的個(gè)數(shù)沒有限制 每次某個(gè)函數(shù)被調(diào)用,就會(huì)有個(gè)新的局部執(zhí)行環(huán)境為其創(chuàng)建,即使是多次調(diào)用的自身函數(shù)(即一個(gè)函數(shù)被調(diào)用多次,也會(huì)創(chuàng)建多個(gè)不同的局部執(zhí)行環(huán)境)。
執(zhí)行環(huán)境的分類Global Code,即全局的、不在任何函數(shù)里面的代碼,例如:一個(gè)js文件、嵌入在HTML頁(yè)面中的js代碼等。 Function Code,即用戶自定義函數(shù)中的函數(shù)體JS代碼。 Eval Code,即使用eval()函數(shù)動(dòng)態(tài)執(zhí)行的JS代碼。【不推薦,可忽略】 或 全局環(huán)境:JavaScript代碼運(yùn)行起來會(huì)首先進(jìn)入該環(huán)境 函數(shù)環(huán)境:當(dāng)函數(shù)被調(diào)用執(zhí)行時(shí),會(huì)進(jìn)入當(dāng)前函數(shù)中執(zhí)行代碼 eval(不建議使用,可忽略)
解析: 全局執(zhí)行環(huán)境 在瀏覽器中,其指window對(duì)象,是JS代碼開始運(yùn)行時(shí)的默認(rèn)環(huán)境。 全局執(zhí)行環(huán)境的變量對(duì)象始終都是作用域鏈中的最后一個(gè)對(duì)象。 函數(shù)執(zhí)行環(huán)境 當(dāng)某個(gè)函數(shù)被調(diào)用時(shí),會(huì)先創(chuàng)建一個(gè)執(zhí)行環(huán)境及相應(yīng)的作用域鏈。然后使用arguments和其他命名參數(shù)的值來初始化執(zhí)行環(huán)境的變量對(duì)象。
執(zhí)行上下文(execution context)屬性: 【注】: 變量對(duì)象是與執(zhí)行上下文相關(guān)的數(shù)據(jù)作用域,存儲(chǔ)了在上下文中定義的變量和函數(shù)聲明。 全局上下文中的變量對(duì)象就是全局對(duì)象 在函數(shù)上下文中,我們用活動(dòng)對(duì)象(activation object, AO)來表示變量對(duì)象。 活動(dòng)對(duì)象和變量對(duì)象其實(shí)是一個(gè)東西,只是變量對(duì)象是規(guī)范上的或者說是引擎實(shí)現(xiàn)上的,不可在 JavaScript 環(huán)境中訪問,只有到當(dāng)進(jìn)入一個(gè)執(zhí)行上下文中,這個(gè)執(zhí)行上下文的變量對(duì)象才會(huì)被激活,所以才叫 activation object 吶,而只有被激活的變量對(duì)象,也就是活動(dòng)對(duì)象上的各種屬性才能被訪問。 活動(dòng)對(duì)象是在進(jìn)入函數(shù)上下文時(shí)刻被創(chuàng)建的,它通過函數(shù)的 arguments 屬性初始化。arguments 屬性值是 Arguments 對(duì)象。 ****** AO & VO ******* AO = VO function parameters arguments AO 還包含函數(shù)的 parameters,以及 arguments 這個(gè)特殊對(duì)象 未進(jìn)入執(zhí)行階段之前,變量對(duì)象(VO)中的屬性都不能訪問!但是進(jìn)入執(zhí)行階段之后,變量對(duì)象(VO)被激活轉(zhuǎn)變?yōu)榱嘶顒?dòng)對(duì)象(AO),里面的屬性都能被訪問了,然后開始進(jìn)行執(zhí)行階段的操作。 它們其實(shí)都是同一個(gè)對(duì)象,只是處于執(zhí)行上下文的不同生命周期 函數(shù)參數(shù)的傳遞賦值問題 與 this的指向問題函數(shù)參數(shù)的傳遞賦值問題 function foo() {
console.log(this.a)
}
function active(fn) {
fn(); // 真實(shí)調(diào)用者,為獨(dú)立調(diào)用
}
var a = 20;
var obj = {
a: 10,
getA: foo
}
active(obj.getA); // 20
var name = "window";
var p = {
name: 'Perter',
getName: function() {
// 利用變量保存的方式保證其訪問的是p對(duì)象
var self = this;
return function() {
console.log(this.name) // window
return self.name;
}
}
}
var getName = p.getName();
var _name = getName();
console.log(_name); // Perter
this指向var obj = {
a: 1,
b: function(){
console.log(this);
}
}
1、作為對(duì)象調(diào)用時(shí),指向該對(duì)象 obj.b(); // 指向obj 2、作為函數(shù)調(diào)用, var b = obj.b; b(); // 指向全局window 3、作為構(gòu)造函數(shù)調(diào)用 var b = new obj.b(); // this指向當(dāng)前實(shí)例對(duì)象 4、作為call與apply調(diào)用 obj.b.apply(object, []); // this指向當(dāng)前的object 作用域鏈創(chuàng)建詞法作用域(lexical scoping)是指,函數(shù)在執(zhí)行時(shí),使用的是它被定義時(shí)的作用域,而不是這個(gè)函數(shù)被調(diào)用時(shí)的作用域
函數(shù)的作用域在函數(shù)定義的時(shí)候就決定了。
這是因?yàn)楹瘮?shù)有一個(gè)內(nèi)部屬性 [[scope]],當(dāng)函數(shù)創(chuàng)建的時(shí)候,就會(huì)保存所有父變量對(duì)象到其中,你可以理解 [[scope]] 就是所有父變量對(duì)象的層級(jí)鏈,但是注意:[[scope]] 并不代表完整的作用域鏈!
當(dāng)函數(shù)激活時(shí),進(jìn)入函數(shù)上下文,創(chuàng)建 VO/AO 后,就會(huì)將活動(dòng)對(duì)象添加到作用鏈的前端。至此,作用域鏈創(chuàng)建完畢。
demo: ```javascipt function foo() { function bar() { ... } } // 函數(shù)創(chuàng)建時(shí),各自的[[scope]]為: foo.[[scope]] = [
globalContext.VO
];
bar.[[scope]] = [
fooContext.AO,
globalContext.VO
];
```
函數(shù)執(zhí)行上下文中作用域鏈和變量對(duì)象的創(chuàng)建過程總結(jié) ```
var scope = "global scope";
function checkscope(){
var scope2 = 'local scope';
return scope2;
}
checkscope();
```
執(zhí)行過程如下:
1.checkscope 函數(shù)被創(chuàng)建,保存作用域鏈到 內(nèi)部屬性[[scope]]
```
checkscope.[[scope]] = [
globalContext.VO
];
```
2.執(zhí)行 checkscope 函數(shù),創(chuàng)建 checkscope 函數(shù)執(zhí)行上下文,checkscope 函數(shù)執(zhí)行上下文被壓入執(zhí)行上下文棧
```
ECStack = [
checkscopeContext,
globalContext
];
```
3.checkscope 函數(shù)并不立刻執(zhí)行,開始做準(zhǔn)備工作,第一步:復(fù)制函數(shù)[[scope]]屬性創(chuàng)建作用域鏈
```
checkscopeContext = {
Scope: checkscope.[[scope]],
}
```
4.第二步:用 arguments 創(chuàng)建活動(dòng)對(duì)象,隨后初始化活動(dòng)對(duì)象,加入形參、函數(shù)聲明、變量聲明
```
checkscopeContext = {
AO: {
arguments: {
length: 0
},
scope2: undefined
},
Scope: checkscope.[[scope]],
}
```
5.第三步:將活動(dòng)對(duì)象壓入 checkscope 作用域鏈頂端
```
checkscopeContext = {
AO: {
arguments: {
length: 0
},
scope2: undefined
},
Scope: [AO, [[Scope]]]
}
```
6.準(zhǔn)備工作做完,開始執(zhí)行函數(shù),隨著函數(shù)的執(zhí)行,修改 AO 的屬性值
```
checkscopeContext = {
AO: {
arguments: {
length: 0
},
scope2: 'local scope'
},
Scope: [AO, [[Scope]]]
}
```
7.查找到 scope2 的值,返回后函數(shù)執(zhí)行完畢,函數(shù)上下文從執(zhí)行上下文棧中彈出
```
ECStack = [
globalContext
];
```
### 閉包
* 即使創(chuàng)建它的上下文已經(jīng)銷毀,它仍然存在(比如,內(nèi)部函數(shù)從父函數(shù)中返回)
* 在代碼中引用了自由變量
內(nèi)部函數(shù)引用了外部函數(shù)的變量,在外部函數(shù)上下文被銷毀后,其中的變量仍然可以被其內(nèi)部函數(shù)引用
因?yàn)椋?/code>
fContext = { Scope: [AO, checkscopeContext.AO, globalContext.VO], } ``` 對(duì)的,就是因?yàn)檫@個(gè)作用域鏈,f 函數(shù)依然可以讀取到 checkscopeContext.AO 的值,說明當(dāng) f 函數(shù)引用了 checkscopeContext.AO 中的值的時(shí)候,即使 checkscopeContext 被銷毀了,但是 JavaScript 依然會(huì)讓 checkscopeContext.AO 活在內(nèi)存中,f 函數(shù)依然可以通過 f 函數(shù)的作用域鏈找到它,正是因?yàn)?JavaScript 做到了這一點(diǎn),從而實(shí)現(xiàn)了閉包這個(gè)概念。
|