javascript之執(zhí)行上下文

本篇文章參考了https://github.com/mqyqingfeng/Blog/issues/5 這位大佬的github。

js在執(zhí)行一段可執(zhí)行代碼的時(shí)候會創(chuàng)建一個(gè)執(zhí)行上下文,對于每一個(gè)執(zhí)行上下文都有三個(gè)重要屬性。

  • 變量對象(Variable object,VO)
  • 作用域鏈(Scope chain)
  • this
在全局上下文中,它的變量對象就是全局對象。

函數(shù)上下文的變量對象也叫活動對象。因?yàn)樽兞繉ο蟛豢稍L問,只有進(jìn)入一個(gè)執(zhí)行上下文,變量對象別激活后,才能被訪問,所以我們叫他活動對象。
執(zhí)行上下文的執(zhí)行過程分為兩段

進(jìn)入執(zhí)行上下文

在這個(gè)階段中,執(zhí)行上下文會分別創(chuàng)建變量對象,建立作用域鏈,以及確定this的指向。

執(zhí)行代碼

創(chuàng)建完成之后,就會開始執(zhí)行代碼,這個(gè)時(shí)候,會完成變量賦值,函數(shù)引用,以及執(zhí)行其他代碼。

當(dāng)進(jìn)入執(zhí)行上下文時(shí),這時(shí)候還沒有執(zhí)行代碼,
變量對象會包括:

  1. 函數(shù)的所有形參 (如果是函數(shù)上下文)
    • 由名稱和對應(yīng)值組成的一個(gè)變量對象的屬性被創(chuàng)建
    • 沒有實(shí)參,屬性值設(shè)為 undefined
  2. 函數(shù)聲明
    • 由名稱和對應(yīng)值(函數(shù)對象(function-object))組成一個(gè)變量對象的屬性被創(chuàng)建
    • 如果變量對象已經(jīng)存在相同名稱的屬性,則完全替換這個(gè)屬性
  1. 變量聲明
    • 由名稱和對應(yīng)值(undefined)組成一個(gè)變量對象的屬性被創(chuàng)建;
    • 如果變量名稱跟已經(jīng)聲明的形式參數(shù)或函數(shù)相同,則變量聲明不會干擾已經(jīng)存在的這類屬性

讓我們來看個(gè)例子

function foo(a) {
  var b = 2;
  function c() {}
  var d = function() {};

  b = 3;

}

foo(1);

在進(jìn)入執(zhí)行上下文后,這時(shí)候的 AO 是:

AO = {
    arguments: {
        0: 1,
        length: 1
    },
    a: 1,
    b: undefined,
    c: reference to function c(){},
    d: undefined
}

在代碼執(zhí)行階段,會順序執(zhí)行代碼,根據(jù)代碼,修改變量對象的值

還是上面的例子,當(dāng)代碼執(zhí)行完后,這時(shí)候的 AO 是:

AO = {
    arguments: {
        0: 1,
        length: 1
    },
    a: 1,
    b: 3,
    c: reference to function c(){},
    d: reference to FunctionExpression "d"
}

我們再來看看arguments對象是什么,引用js高程里的話:

調(diào)用函數(shù)時(shí),會為其創(chuàng)建一個(gè)Arguments對象,并自動初始化局部變量arguments,指代該Arguments對象。所有作為參數(shù)傳入的值都會成為Arguments對象的數(shù)組元素。

當(dāng)查找變量的時(shí)候,會先從當(dāng)前上下文的變量對象中查找,如果沒有找到,就會從父級(詞法層面上的父級)執(zhí)行上下文的變量對象中查找,一直找到全局上下文的變量對象,也就是全局對象。這樣由多個(gè)執(zhí)行上下文的變量對象構(gòu)成的鏈表就叫做作用域鏈。
我們來看例子。

var scope = "global scope";

  function checkscope() {
    var scope2 = 'local scope';
    return scope2;
  }

  checkscope();
 //1.checkscope被創(chuàng)建
 //保存父元素的變量對象到他的內(nèi)部屬性[[scope]]上
 checkscope.[[scope]] = [globalContext.VO]
// 2.開始執(zhí)行checkscope函數(shù) 創(chuàng)建checkscope的執(zhí)行上下文,同時(shí)chec
 ECStack = [
  checkscopeContext,
  globalContext
 ]
 //3.checkscope函數(shù)還需要做一些準(zhǔn)備工作才能執(zhí)行
 // 第一步,復(fù)制[[scope]]屬性創(chuàng)建作用域鏈
   checkscopeContext = {
     Scope: checkscope.[[scope]]
  }
  // 第二步,用arguments創(chuàng)建活動對象,并初始化
  checkscopeContext = {
    Ao: {
      arguments: {
        length: 0
      },
       scope2: undefined
    },
     Scope: checkscope.[[scope]]
   }
  // 第三步,把自己的活動對象壓入Scope中
   checkscopeContext = {
    AO: {
      arguments: {
         length: 0
       },
       scope2: undefined
    },
    Scope: [AO, checkscope.[[scope]]]
   }
// 4.終于準(zhǔn)備工作做完,可以開始執(zhí)行函數(shù),修改AO的屬性值
 checkscopeContext = {
     AO: {
          arguments: {
            length: 0
          },
     scope2: 'local scope'
     },
     Scope: [AO, [[Scope]]]
 }
// 5.查到了scope2的值,返回后函數(shù) 執(zhí)行完畢,把函數(shù)上下文從執(zhí)行上下文中彈出
 ECStack = [
  globalContext
 ];

再來看一個(gè)

function a() {
   var aaa = 123;
   function b(){ //函數(shù)b的創(chuàng)建是在a的執(zhí)行上下文準(zhǔn)備階段創(chuàng)建的,這時(shí)候就有了a的AO,所以b創(chuàng)建的時(shí)候b.[[scope]] = [a.Ao] = 
      console.log(aaa); 
      aaa=234;
};
   b();
   console.dir(aaa);
};
a();

對于上面的例子

  1. function b有自己的作用域,內(nèi)部定義了aaa,所以在它的VO中aaa是undefined
  2. function a的VO中也定義了aaa,值是123;
  3. function b執(zhí)行時(shí)會順著它的作用域鏈做變量查找(reference resolution),先找 到自己定義的aaa,輸出是undefined,因?yàn)樽约旱腣O在作用域鏈的第一個(gè)位置,最先被查找。
  4. 自然,運(yùn)行時(shí)修改的也是b自己AO中的aaa,所以不會影響到a中的aaa。

執(zhí)行函數(shù)的時(shí)候才開始創(chuàng)建上下文和執(zhí)行上下文?。。?/h1>

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容