繼續接着上篇文章,上篇咱們說到函數上下文的結構可表示爲javascript
const ExecutionContextObj = { VO: window, // 變量對象 ScopeChain: {}, // 做用域鏈 this: window };
即每一個函數上下文,都要有這三個重要屬性:java
今天再細說執行上下文中的變量對象函數
變量對象是與執行上下文的相關的數據做用域,存儲了在上下文中定義的變量和函數聲明。由於不一樣執行上下文的變量對象略有不一樣,因此變量對象通常分爲全局上下文下的變量對象和函數上下文下的變量對象。this
變量對象的建立,屬於執行上下文中的建立階段,依次通過如下三個過程:線程
建立執行上下文有兩個階段:一個是建立階段,一個是執行階段。變量對象的建立,屬於執行上下文中的建立階段,會依次通過三個過程:code
在進入函數執行上下文時,會首先檢查實參個數,接着對實參對象和形參進行賦值。若是沒有實參,屬性值設爲undefined;當傳入的實參數量小於形參數量,則會將沒有被賦值的形參賦值爲 undefined。對象
function fn(a, b, c){ console.log(a, b, c); // 1 2 undefined } fn(1, 2);
此時變量對象的結構爲:ip
VO = { a: 1, b: 2, c: undefined }
遇到同名的函數時,後面函數會覆蓋前面的函數。作用域
function fn() { console.log('先聲明的'); } function fn() { console.log('後聲明的'); } console.log(fn); //ƒ fn() { console.log('後聲明的'); }
檢查當前環境中經過變量聲明(var)並賦值爲undefined(變量提高產生的緣由)io
console.log(fn); // ƒ fn() { console.log('後聲明的');} console.log(b); // undefined function fn() { console.log('先聲明的'); } function fn() { console.log('後聲明的'); } var b = 10; var fn = 20; console.log(b); // 10
由上面咱們看出,當變量名稱與函數名稱同名時,會忽略此變量聲明,即同名時,函數聲明優先
js雖然單線程的語言,執行順序爲順序執行,但JS引擎並非一行一行地分析和執行程序,而是一段一段地分析執行。
讓咱們從JS引擎的角度理一理上述三個過程:函數提高和變量提高,是全局執行上下文作的準備工做;當執行函數時,又會建立一個執行上下文,作的是這個函數內部的準備工做;這就是JS引擎分析代碼的"預編譯階段",作完該工做才進入執行階段。
在客戶端 JavaScript 中,全局對象就是 Window 對象;
var a = 2; //在全局上下文使用var定義變量a,做爲全局變量的宿主 console.log(this); // window對象 console.log(this.a); // 2
對於全局上下文中來講,變量對象就是全局對象,
執行上下文的第二個階段爲執行階段,此時會進行變量賦值,執行其餘代碼等工做,此時,變量對象變爲活動對象(Active object, AO)。
活動對象和變量對象實際上是同個東西,只是規範概念上的差別。只有到當進入一個執行上下文中,這個執行上下文的變量對象纔會被激活,因此才叫活動對象。而只有被激活的變量對象,也就是活動對象上的各類屬性才能被訪問。
因此明確,活動對象是在進入函數上下文時刻被建立的,它經過函數的 arguments 屬性初始化。
console.log(fn); // ƒ fn() { console.log('後聲明的');} console.log(b); // undefined function fn() { console.log('先聲明的'); } function fn() { console.log('後聲明的'); } var b = 10; console.log(b); // 10 var fn = 20; console.log(fn);//20
上述代碼,真正開始執行是從第一行console.log(fn)。在此以前,變量對象VO是這樣的:
// 建立過程 EC= { VO:{}, // 建立變量對象 scopeChain: [{VO}], // 做用域鏈 this: window // this綁定 } VO = { // argument: {}, // 當前爲全局上下文,不存在arguments fn: reference to function fn(){}, // 函數fn的引用地址 b: undefiend // 變量提高 }
根據變量對象建立的三個過程,
到此,變量對象的建立階段完成,接下來進行執行階段:
1.執行console.log(fn);此時fn爲聲明的第二個函數,故輸出結果:"後聲明的"。 2.執行console.log(b),此時b已被賦值爲undefined,故輸出結果:"undefined"。 3.執行賦值操做: b = 10; 4.執行console.log(b) ,故輸出b爲10。 5.執行賦值操做: fn = 20; 6.執行console.log(fn) ,故輸出fn爲20。
執行到最後一步時,執行上下文以下:
// 執行階段 EC = { VO = {}; scopeChain: {}; this: window; } // VO ---- AO AO = { argument: {}; fn: 20; b: 10; }
以上,就是變量對象在代碼執行前及執行後的變化。