国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

解密JavaScript執(zhí)行上下文

JeOam / 690人閱讀

摘要:執(zhí)行上下文棧首先我們先了解一下什么是執(zhí)行上下文棧。那么隨著我們的執(zhí)行上下文數(shù)量的增加,引擎又如何去管理這些執(zhí)行上下文呢這時便有了執(zhí)行上下文棧。這樣由多個執(zhí)行上下文的變量對象構(gòu)成的鏈表就叫做作用域鏈。

執(zhí)行上下文棧

首先我們先了解一下什么是執(zhí)行上下文棧(Execution context stack)。


上面這張圖來自于mdn,分別展示了棧、堆和隊列,其中棧就是我們所說的執(zhí)行上下文棧;堆是用于存儲對象這種復(fù)雜類型,我們復(fù)制對象的地址引用就是這個堆內(nèi)存的地址;隊列就是異步隊列,用于event loop的執(zhí)行。

JS代碼在引擎中是以“一段一段”的方式來分析執(zhí)行的,而并非一行一行來分析執(zhí)行。而這“一段一段”的可執(zhí)行代碼無非為三種:Global codeFunction CodeEval code。這些可執(zhí)行代碼在執(zhí)行的時候又會創(chuàng)建一個一個的執(zhí)行上下文(Execution context)。例如,當執(zhí)行到一個函數(shù)的時候,JS引擎會做一些“準備工作”,而這個“準備工作”,我們稱其為執(zhí)行上下文

那么隨著我們的執(zhí)行上下文數(shù)量的增加,JS引擎又如何去管理這些執(zhí)行上下文呢?這時便有了執(zhí)行上下文棧。

這里我用一段貫穿全文的例子來講解執(zhí)行上下文棧的執(zhí)行過程:

var scope = "global scope";

function checkscope(s) {
  var scope = "local scope";

  function f() {
    return scope;
  }
  return f();
}
checkscope("scope");

當JS引擎去解析代碼的時候,最先碰到的就是Global code,所以一開始初始化的時候便會將全局上下文推入執(zhí)行上下文棧,并且只有在整個應(yīng)用程序執(zhí)行完畢的時候,全局上下文才會推出執(zhí)行上下文棧。

這里我們用ECS來模擬執(zhí)行上下文棧,用globalContext來表示全局上下文:

ESC = [
  globalContext, // 一開始只有全局上下文
]

然后當代碼執(zhí)行checkscope函數(shù)的時候,會創(chuàng)建checkscope函數(shù)的執(zhí)行上下文,并將其壓入執(zhí)行上下文棧:

ESC = [
  checkscopeContext, // checkscopeContext入棧
  globalContext,
]

接著代碼執(zhí)行到return f()的時候,f函數(shù)的執(zhí)行上下文被創(chuàng)建:

ESC = [
  fContext, // fContext入棧
  checkscopeContext,
  globalContext,
]

f函數(shù)執(zhí)行完畢后,f函數(shù)的執(zhí)行上下文出棧,隨后checkscope函數(shù)執(zhí)行完畢,checkscope函數(shù)的執(zhí)行上下文出棧:

// fContext出棧
ESC = [
  // fContext出棧
  checkscopeContext,
  globalContext,
]

// checkscopeContext出棧
ESC = [
  // checkscopeContext出棧
  globalContext,
]
變量對象

每一個執(zhí)行上下文都有三個重要的屬性:

變量對象

作用域鏈

this

這一節(jié)我們先來說一下變量對象(Variable object,這里簡稱VO)。

變量對象是與執(zhí)行上下文相關(guān)的數(shù)據(jù)作用域,存儲了在上下文中定義的變量和函數(shù)聲明。并且不同的執(zhí)行上下文也有著不同的變量對象,這里分為全局上下文中的變量對象和函數(shù)執(zhí)行上下文中的變量對象。

全局上下文中的變量對象

全局上下文中的變量對象其實就是全局對象。我們可以通過this來訪問全局對象,并且在瀏覽器環(huán)境中,this === window;在node環(huán)境中,this === global

函數(shù)上下文中的變量對象

在函數(shù)上下文中的變量對象,我們用活動對象來表示(activation object,這里簡稱AO),為什么稱其為活動對象呢,因為只有到當進入一個執(zhí)行上下文中,這個執(zhí)行上下文的變量對象才會被激活,并且只有被激活的變量對象,其屬性才能被訪問。

在函數(shù)執(zhí)行之前,會為當前函數(shù)創(chuàng)建執(zhí)行上下文,并且在此時,會創(chuàng)建變量對象:

根據(jù)函數(shù)arguments屬性初始化arguments對象;

根據(jù)函數(shù)聲明生成對應(yīng)的屬性,其值為一個指向內(nèi)存中函數(shù)的引用指針。如果函數(shù)名稱已存在,則覆蓋;

根據(jù)變量聲明生成對應(yīng)的屬性,此時初始值為undefined。如果變量名已聲明,則忽略該變量聲明;

還是以剛才的代碼為例:

var scope = "global scope";

function checkscope(s) {
  var scope = "local scope";

  function f() {
    return scope;
  }
  return f();
}
checkscope("scope");

在執(zhí)行checkscope函數(shù)之前,會為其創(chuàng)建執(zhí)行上下文,并初始化變量對象,此時的變量對象為:

VO = {
  arguments: {
    0: "scope",
    length: 1,
  },
  s: "scope", // 傳入的參數(shù)
  f: pointer to function f(),
  scope: undefined, // 此時聲明的變量為undefined
}

隨著checkscope函數(shù)的執(zhí)行,變量對象被激活,變相對象內(nèi)的屬性隨著代碼的執(zhí)行而改變:

VO = {
  arguments: {
    0: "scope",
    length: 1,
  },
  s: "scope", // 傳入的參數(shù)
  f: pointer to function f(),
  scope: "local scope", // 變量賦值
}

其實也可以用另一個概念“函數(shù)提升”和“變量提升”來解釋:

function checkscope(s) {
  function f() { // 函數(shù)提升
    return scope;
  }
  var scope; // 變量聲明提升

  scope = "local scope" // 變量對象的激活也相當于此時的變量賦值

  return f();
}
作用域鏈

每一個執(zhí)行上下文都有三個重要的屬性:

變量對象

作用域鏈

this

這一節(jié)我們說一下作用域鏈。

什么是作用域鏈

當查找變量的時候,會先從當前上下文的變量對象中查找,如果沒有找到,就會從父級執(zhí)行上下文的變量對象中查找,一直找到全局上下文的變量對象。這樣由多個執(zhí)行上下文的變量對象構(gòu)成的鏈表就叫做作用域鏈。

下面還是用我們的例子來講解作用域鏈:

var scope = "global scope";

function checkscope(s) {
  var scope = "local scope";

  function f() {
    return scope;
  }
  return f();
}
checkscope("scope");

首先在checkscope函數(shù)聲明的時候,內(nèi)部會綁定一個[[scope]]的內(nèi)部屬性:

checkscope.[[scope]] = [
  globalContext.VO
];

接著在checkscope函數(shù)執(zhí)行之前,創(chuàng)建執(zhí)行上下文checkscopeContext,并推入執(zhí)行上下文棧:

復(fù)制函數(shù)的[[scope]]屬性初始化作用域鏈;

創(chuàng)建變量對象;

將變量對象壓入作用域鏈的最頂端;

// -> 初始化作用域鏈;
checkscopeContext = {
  scope: checkscope.[[scope]],
}

// -> 創(chuàng)建變量對象
checkscopeContext = {
  scope: checkscope.[[scope]],
  VO = {
    arguments: {
      0: "scope",
      length: 1,
    },
    s: "scope", // 傳入的參數(shù)
    f: pointer to function f(),
    scope: undefined, // 此時聲明的變量為undefined
  },
}

// -> 將變量對象壓入作用域鏈的最頂端
checkscopeContext = {
  scope: [VO, checkscope.[[scope]]],
  VO = {
    arguments: {
      0: "scope",
      length: 1,
    },
    s: "scope", // 傳入的參數(shù)
    f: pointer to function f(),
    scope: undefined, // 此時聲明的變量為undefined
  },
}

接著,隨著函數(shù)的執(zhí)行,修改變量對象:

checkscopeContext = {
  scope: [VO, checkscope.[[scope]]],
  VO = {
    arguments: {
      0: "scope",
      length: 1,
    },
    s: "scope", // 傳入的參數(shù)
    f: pointer to function f(),
    scope: "local scope", // 變量賦值
  }
}

與此同時遇到f函數(shù)聲明,f函數(shù)綁定[[scope]]屬性:

checkscope.[[scope]] = [
  checkscopeContext.VO, // f函數(shù)的作用域還包括checkscope的變量對象
  globalContext.VO
];

之后f函數(shù)的步驟同checkscope函數(shù)。

再來一個經(jīng)典的例子:

var data = [];

for (var i = 0; i < 6; i++) {
  data[i] = function () {
    console.log(i);
  };
}

data[0]();
// ...

很簡單,不管訪問data幾,最終console打印出來的都是6,因為在ES6之前,JS都沒有塊級作用域的概念,for循環(huán)內(nèi)的代碼都在全局作用域下。

在data函數(shù)執(zhí)行之前,此時全局上下文的變量對象為:

globalContext.VO = {
  data: [pointer to function ()],
  i: 6, // 注意:此時的i值為6
}

每一個data匿名函數(shù)的執(zhí)行上下文鏈大致都如下:

data[n]Context = {
  scope: [VO, globalContext.VO],
  VO: {
    arguments: {
      length: 0,
    }
  }
}

那么在函數(shù)執(zhí)行的時候,會先去自己匿名函數(shù)的變量對象上找i的值,發(fā)現(xiàn)沒有后會沿著作用域鏈查找,找到了全局執(zhí)行上下文的變量對象,而此時全局執(zhí)行上下文的變量對象中的i為6,所以每一次都打印的是6了。

詞法作用域 & 動態(tài)作用域

JavaScript這門語言是基于詞法作用域來創(chuàng)建作用域的,也就是說一個函數(shù)的作用域在函數(shù)聲明的時候就已經(jīng)確定了,而不是函數(shù)執(zhí)行的時候。

改一下之前的例子:

var scope = "global scope";

function f() {
  console.log(scope)
}

function checkscope() {
  var scope = "local scope";

  f();
}
checkscope();

因為JavaScript是基于詞法作用域創(chuàng)建作用域的,所以打印的結(jié)果是global scope而不是local scope。我們結(jié)合上面的作用域鏈來分析一下:

首先遇到了f函數(shù)的聲明,此時為其綁定[[scope]]屬性:

// 這里就是我們所說的“一個函數(shù)的作用域在函數(shù)聲明的時候就已經(jīng)確定了”
f.[[scope]] = [
  globalContext.VO, // 此時的全局上下文的變量對象中保存著scope = "global scope";
];

然后我們直接跳過checkscope的執(zhí)行上下文的創(chuàng)建和執(zhí)行的過程,直接來到f函數(shù)的執(zhí)行上。此時在函數(shù)執(zhí)行之前初始化f函數(shù)的執(zhí)行上下文:

// 這里就是為什么會打印global scope
fContext = {
  scope: [VO, globalContext.VO], // 復(fù)制f.[[scope]],f.[[scope]]只有全局執(zhí)行上下文的變量對象
  VO = {
    arguments: {
      length: 0,
    },
  },
}

然后到了f函數(shù)執(zhí)行的過程,console.log(scope),會沿著f函數(shù)的作用域鏈查找scope變量,先是去自己執(zhí)行上下文的變量對象中查找,沒有找到,然后去global執(zhí)行上下文的變量對象上查找,此時scope的值為global scope

this

在這里this綁定也可以分為全局執(zhí)行上下文和函數(shù)執(zhí)行上下文:

在全局執(zhí)行上下文中,this的指向全局對象。(在瀏覽器中,this引用 Window 對象)。

在函數(shù)執(zhí)行上下文中,this 的值取決于該函數(shù)是如何被調(diào)用的。如果它被一個引用對象調(diào)用,那么this會被設(shè)置成那個對象,否則this的值被設(shè)置為全局對象或者undefined(在嚴格模式下)

總結(jié)起來就是,誰調(diào)用了,this就指向誰。

執(zhí)行上下文

這里,根據(jù)之前的例子來完整的走一遍執(zhí)行上下文的流程:

var scope = "global scope";

function checkscope(s) {
  var scope = "local scope";

  function f() {
    return scope;
  }
  return f();
}
checkscope("scope");

首先,執(zhí)行全局代碼,創(chuàng)建全局執(zhí)行上下文,并且全局執(zhí)行上下文進入執(zhí)行上下文棧:

globalContext = {
  scope: [globalContext.VO],
  VO: global,
  this: globalContext.VO
}

ESC = [
  globalContext,
]

然后隨著代碼的執(zhí)行,走到了checkscope函數(shù)聲明的階段,此時綁定[[scope]]屬性:

checkscope.[[scope]] = [
  globalContext.VO,
]

在checkscope函數(shù)執(zhí)行之前,創(chuàng)建checkscope函數(shù)的執(zhí)行上下文,并且checkscope執(zhí)行上下文入棧:

// 創(chuàng)建執(zhí)行上下文
checkscopeContext = {
  scope: [VO, globalContext.VO], // 復(fù)制[[scope]]屬性,然后VO推入作用域鏈頂端
  VO = {
    arguments: {
      0: "scope",
      length: 1,
    },
    s: "scope", // 傳入的參數(shù)
    f: pointer to function f(),
    scope: undefined,
  },
  this: globalContext.VO,
}

// 進入執(zhí)行上下文棧
ESC = [
  checkscopeContext,
  globalContext,
]

checkscope函數(shù)執(zhí)行,更新變量對象:

// 創(chuàng)建執(zhí)行上下文
checkscopeContext = {
  scope: [VO, globalContext.VO], // 復(fù)制[[scope]]屬性,然后VO推入作用域鏈頂端
  VO = {
    arguments: {
      0: "scope",
      length: 1,
    },
    s: "scope", // 傳入的參數(shù)
    f: pointer to function f(),
    scope: "local scope", // 更新變量
  },
  this: globalContext.VO,
}

f函數(shù)聲明,綁定[[scope]]屬性:

f.[[scope]] = [
  checkscopeContext.VO,
  globalContext.VO,
]

f函數(shù)執(zhí)行,創(chuàng)建執(zhí)行上下文,推入執(zhí)行上下文棧:

// 創(chuàng)建執(zhí)行上下文
fContext = {
  scope: [VO, checkscopeContext.VO, globalContext.VO], // 復(fù)制[[scope]]屬性,然后VO推入作用域鏈頂端
  VO = {
    arguments: {
      length: 0,
    },
  },
  this: globalContext.VO,
}

// 入棧
ESC = [
  fContext,
  checkscopeContext,
  globalContext,
]

f函數(shù)執(zhí)行完成,f函數(shù)執(zhí)行上下文出棧,checkscope函數(shù)執(zhí)行完成,checkscope函數(shù)出棧:

ESC = [
  // fContext出棧
  checkscopeContext,
  globalContext,
]

ESC = [
  // checkscopeContext出棧,
  globalContext,
]

到此,一個整體的執(zhí)行上下文的流程就分析完了。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/109683.html

相關(guān)文章

  • 解密 JavaScript 執(zhí)行下文

    摘要:閉包就好像從中分離出來的一個充滿神秘色彩的未開化世界,只有最勇敢的人才能到達那里。興奮地趕緊自測咔咔咔連點三下。結(jié)果當時內(nèi)心表情大概就像上面這個哥們。但還是在工位上故作鎮(zhèn)定地趕緊百度了下。 ? 閉包就好像從JavaScript中分離出來的一個充滿神秘色彩的未開化世界,只有最勇敢的人才能到達那里。——《你不知道的JavaScript 上卷》 1、起源 js閉包很長一...

    khlbat 評論0 收藏0
  • 基礎(chǔ)知識 - 收藏集 - 掘金

    摘要:本文是面向前端小白的,大手子可以跳過,寫的不好之處多多分鐘搞定常用基礎(chǔ)知識前端掘金基礎(chǔ)智商劃重點在實際開發(fā)中,已經(jīng)非常普及了。 JavaScript字符串所有API全解密 - 掘金關(guān)于 我的博客:louis blog SF專欄:路易斯前端深度課 原文鏈接:JavaScript字符串所有API全解密 本文近 6k 字,讀完需 10 分鐘。 字符串作為基本的信息交流的橋梁,幾乎被所有的編程...

    wdzgege 評論0 收藏0
  • 當我們在談?wù)撉岸思用軙r,我們在談些什么

    摘要:所以我們今天只談前端加密,一個部分人認為沒有意義的工作。在中,認證過程使用了非對稱加密算法,非認證過程中使用了對稱加密算法。非對稱加密上文中我們討論了前端的哈希加密以及應(yīng)用的場景。 showImg(https://segmentfault.com/img/bVAhTC); 當然在談安全。 前端安全是Web安全的一部分,常見的安全問題會有XSS、CSRF、SQL注入等,然而這些已經(jīng)在程師...

    wizChen 評論0 收藏0
  • 系統(tǒng)潛入后門分析

    摘要:在這個案例里,這些是欺騙性的功能,它們似乎有一個唯一目的,即混淆自動檢測系統(tǒng),反病毒軟件,或者那些甚至嘗試手工分析這些程序樣本的分析人員。受害機器的處于所規(guī)定的地址空間,攻擊者是無法通過到達的。 初始傳染手段?-?Nuclear?Pack 已經(jīng)有一些其他的文章介紹過Nuclear?Pack破解工具包。可能它還不像g10pack或者BlackHole這些工具那么流行,也沒有像CoolE...

    forrest23 評論0 收藏0
  • 【譯】JavaScript中的執(zhí)行下文和堆棧是什么?

    摘要:每次調(diào)用函數(shù)時,都會創(chuàng)建一個新的執(zhí)行上下文。理解執(zhí)行上下文和堆棧可以讓您了解代碼為什么要計算您最初沒有預(yù)料到的不同值的原因。 首發(fā):https://www.love85g.com/?p=1723 在這篇文章中,我將深入研究JavaScript最基本的部分之一,即執(zhí)行上下文。在這篇文章的最后,您應(yīng)該更清楚地了解解釋器要做什么,為什么在聲明一些函數(shù)/變量之前可以使用它們,以及它們的值是如何...

    miguel.jiang 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<