深刻理解JavaScript之執行上下文和變量對象

繼續接着上篇文章,上篇咱們說到函數上下文的結構可表示爲javascript

const ExecutionContextObj = {
    VO: window,     // 變量對象
    ScopeChain: {}, // 做用域鏈
    this: window
};
複製代碼

即每一個函數上下文,都要有這三個重要屬性:java

  • 變量對象(Variable object, VO)
  • 做用域鏈(Scope chain)
  • this

今天再細說執行上下文中的變量對象bash

變量對象

1.什麼是變量對象

變量對象是與執行上下文的相關的數據做用域,存儲了在上下文中定義的變量和函數聲明。由於不一樣執行上下文的變量對象略有不一樣,因此變量對象通常分爲全局上下文下的變量對象和函數上下文下的變量對象。函數

2.變量對象(VO)的建立過程

變量對象的建立,屬於執行上下文中的建立階段,依次通過如下三個過程:ui

建立執行上下文有兩個階段:一個是建立階段,一個是執行階段。變量對象的建立,屬於執行上下文中的建立階段,會依次通過三個過程:this

1.爲函數的形參賦值(函數上下文)

在進入函數執行上下文時,會首先檢查實參個數,接着對實參對象和形參進行賦值。若是沒有實參,屬性值設爲undefined;當傳入的實參數量小於形參數量,則會將沒有被賦值的形參賦值爲 undefined。spa

function fn(a, b, c){
    console.log(a, b, c); // 1 2 undefined
}
fn(1, 2);
複製代碼

此時變量對象的結構爲:線程

VO = {
    a: 1,
    b: 2,
    c: undefined
}
複製代碼

2.函數聲明

遇到同名的函數時,後面函數會覆蓋前面的函數。code

function fn() {
    console.log('先聲明的');
}
function fn() {
  console.log('後聲明的');
}

console.log(fn); //ƒ fn() { console.log('後聲明的'); }
複製代碼

3.變量聲明

檢查當前環境中經過變量聲明(var)並賦值爲undefined(變量提高產生的緣由)對象

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引擎分析代碼的"預編譯階段",作完該工做才進入執行階段。

3.全局上下文的變量對象

在客戶端 JavaScript 中,全局對象就是 Window 對象;

var a = 2; //在全局上下文使用var定義變量a,做爲全局變量的宿主
console.log(this); // window對象
console.log(this.a); // 2 
複製代碼

對於全局上下文中來講,變量對象就是全局對象,

4.變量對象變爲活動對象

執行上下文的第二個階段爲執行階段,此時會進行變量賦值,執行其餘代碼等工做,此時,變量對象變爲活動對象(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. 首先是arguments對象的建立(全局上下文沒有則忽略)
  2. 其次,是檢查函數的聲明。此時,函數fn聲明瞭兩次,則後一次的聲明會覆蓋上一次的聲明。
  3. 最後,是檢查變量的聲明,先聲明瞭變量b,將它賦值爲 undefined;接着遇到fn的變量聲明,因爲fn已經被聲明爲一個函數,故忽略該變量聲明。

到此,變量對象的建立階段完成,接下來進行執行階段:

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;
}
複製代碼

以上,就是變量對象在代碼執行前及執行後的變化。

總結

  1. 全局上下文的變量對象初始化是全局對象

  2. 函數上下文的變量對象初始化只包括 arguments 對象

  3. 在進入執行上下文時會給變量對象添加形參、函數聲明、變量聲明等初始的屬性值

  4. 在代碼執行階段,會再次修改變量對象的屬性值

相關文章
相關標籤/搜索