成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

“動(dòng)靜結(jié)合” 小白初探靜態(tài)(詞法)作用域,作用域鏈與執(zhí)行環(huán)境(EC)

Drummor / 3593人閱讀

摘要:圖片中的作用域鏈,是全局執(zhí)行環(huán)境中的作用域鏈。然后此活動(dòng)對(duì)象被推入作用域鏈的最前端。在最后調(diào)用的時(shí)候,創(chuàng)建先構(gòu)建作用域鏈,再創(chuàng)建執(zhí)行環(huán)境,再創(chuàng)建執(zhí)行環(huán)境的時(shí)候發(fā)現(xiàn)了一個(gè)變量標(biāo)識(shí)符。

從圖書(shū)館翻過(guò)各種JS的書(shū)之后,對(duì)作用域/執(zhí)行環(huán)境/閉包這些概念有了一個(gè)比較清晰的認(rèn)識(shí)。

栗子說(shuō)明一切 第一個(gè)栗子

來(lái)看一個(gè)來(lái)自ECMA-262的栗子:

var x = 10;

(function foo() {
  var y = 20;
  (function bar() {
    var z = 30;
    // "x" and "y" are "free variables"
    // and are found in the next (after
    // bar"s activation object) object
    // of the bar"s scope chain
    console.log(x + y + z);
  })();
})();

我們可以用下圖展現(xiàn)上面的例子(父變量對(duì)象存儲(chǔ)在函數(shù)的Scope屬性內(nèi))

首先,可以很容易的理解到一個(gè)事實(shí):在從控制臺(tái)輸出x+y+z的時(shí)候,xy是在bar()函數(shù)中的作用域鏈中bar()的活動(dòng)對(duì)象之下找到的。實(shí)際上,foo()函數(shù)和bar()函數(shù)在執(zhí)行的時(shí)候,他們的scope屬性就已經(jīng)確定了,他們的scope屬性確定為他們外層的變量對(duì)象(VO)的集合。從圖中可知,內(nèi)存結(jié)構(gòu)可能是這樣的:

// foo的scope屬性是global的VO
foo.["[[Scope]]"] = { global.["Variable Object"] }

// bar的scope屬性是foo的AO和global的VO的集合
bar.["[[Scope]]"] = {foo.["Activation Object"], global.["Variable Object"]}
第二個(gè)栗子

這個(gè)例子來(lái)自《高性能Javascript》

// 全局范圍定義
function add(num1, num2) {
  var sum = num1 + num2;
  return sum;
}

當(dāng)add()函數(shù)創(chuàng)建的時(shí)候,它的scope屬性被確定為全局對(duì)象的VO,這個(gè)全局對(duì)象的VO可能包括window/navigator/document之類等等。關(guān)系如圖:

這個(gè)scope屬性很特別,他是靜態(tài)的,在函數(shù)創(chuàng)建的時(shí)候便能確定。圖片中的作用域鏈,是全局執(zhí)行環(huán)境中的作用域鏈。而在函數(shù)執(zhí)行的時(shí)候,書(shū)中說(shuō)道:

每個(gè)執(zhí)行上下文都有自己的作用域鏈,用于解析標(biāo)識(shí)符。當(dāng)執(zhí)行上下文被創(chuàng)建的時(shí)候,它的作用域鏈初始化為當(dāng)前運(yùn)行函數(shù)的scope屬性中的對(duì)象。這些值按照他們出現(xiàn)在函數(shù)中的順序,被復(fù)制到執(zhí)行上下文的作用域鏈中。這個(gè)過(guò)程一旦完成,一個(gè)被稱為“活動(dòng)對(duì)象(AO)”的新對(duì)象就為執(zhí)行上下文創(chuàng)建好了?;顒?dòng)對(duì)象作為函數(shù)運(yùn)行時(shí)的變量對(duì)象,包含了所有的局部變量,命名參數(shù),參數(shù)集合以及this。然后此活動(dòng)對(duì)象被推入作用域鏈的最前端。

可以了解到,作用域鏈?zhǔn)莻€(gè)鏈表,是在函數(shù)執(zhí)行的時(shí)候才存在的,也就是函數(shù)創(chuàng)建執(zhí)行環(huán)境的時(shí)候才開(kāi)始存在的,它先把這個(gè)函數(shù)的靜態(tài)屬性scope屬性中的所有變量對(duì)象按照順序復(fù)制到作用域鏈(所以這樣就不會(huì)擔(dān)心作用域鏈嵌套的問(wèn)題),然后創(chuàng)建AO放在作用域鏈頂部“0號(hào)位”。例如再執(zhí)行代碼:

var total = add(5, 10);

圖片如下圖:

所以,我們也可以得到一個(gè)驚人的結(jié)論:

函數(shù)作用域鏈 = 活動(dòng)對(duì)象(AO) + scope屬性

關(guān)鍵的來(lái)了

這個(gè)結(jié)論中:活動(dòng)對(duì)象(AO)是臨時(shí)的,動(dòng)態(tài)的,獨(dú)一無(wú)二的。scope屬性是靜態(tài)的,確定的。

所以說(shuō),函數(shù)的作用域鏈,是函數(shù)執(zhí)行的時(shí)候動(dòng)態(tài)創(chuàng)建的,但是它又是基于靜態(tài)詞法的環(huán)境(scope屬性)。所謂“動(dòng)態(tài)創(chuàng)建”,是指在函數(shù)執(zhí)行的時(shí)候,先創(chuàng)建之前沒(méi)有的作用域鏈,再創(chuàng)建活動(dòng)對(duì)象,然后活動(dòng)對(duì)象推入作用域鏈最前端;所謂“基于靜態(tài)的詞法環(huán)境”是指函數(shù)定義的時(shí)候,這個(gè)函數(shù)本是沒(méi)有作用域鏈的,有的只有scope屬性,而這個(gè)屬性指向了這個(gè)函數(shù)外部的執(zhí)行環(huán)境,而這個(gè)外部的執(zhí)行環(huán)境擁有作用域鏈(因?yàn)檫@是外部創(chuàng)建外部的執(zhí)行環(huán)境才擁有作用域鏈的,這樣有一點(diǎn)遞歸的味道)。P.S.其實(shí)有的版本也說(shuō),作用域鏈的確定應(yīng)該是在活動(dòng)變量創(chuàng)建完成之后的,這個(gè)有待鉆研。
P.S 在ES5規(guī)范文檔中,進(jìn)入函數(shù)代碼的流程:

扯到變量提升

變量提升的本質(zhì)就是函數(shù)在創(chuàng)建執(zhí)行環(huán)境中的變量對(duì)象的時(shí)候,記錄下了函數(shù)聲明,變量和參數(shù)等等。具體參見(jiàn)深入理解Javascript之執(zhí)行上下文(Execution Context),下面是片段:

建立Variable Object對(duì)象順序:

建立arguments對(duì)象,檢查當(dāng)前上下文中的參數(shù),建立該對(duì)象下的屬性以及屬性值

檢查當(dāng)前上下文中的函數(shù)聲明: 每找到一個(gè)函數(shù)聲明,就在variableObject下面用函數(shù)名建立一個(gè)屬性,屬性值就是指向該函數(shù)在內(nèi)存中的地址的一個(gè)引用。如果上述函數(shù)名已經(jīng)存在于variableObject下,那么對(duì)應(yīng)的屬性值會(huì)被新的引用所覆蓋。

檢查當(dāng)前上下文中的變量聲明: 每找到一個(gè)變量的聲明,就在variableObject下,用變量名建立一個(gè)屬性,屬性值為undefined。如果該變量名已經(jīng)存在于variableObject屬性中,直接跳過(guò)(防止指向函數(shù)的屬性的值被變量屬性覆蓋為undefined),原屬性值不會(huì)被修改。

扯到閉包

閉包,在離散數(shù)學(xué)中指的是滿足性質(zhì)A的一個(gè)最小關(guān)系集R,這可以理解這個(gè)關(guān)系集R,在性質(zhì)A上封閉。閉包不是一種魔法,雖然可以通過(guò)閉包扯得很遠(yuǎn)很遠(yuǎn),通過(guò)函數(shù)的作用域鏈的組成為AO+scope屬性,為快速理解閉包中變量引用來(lái)自哪里提供了思路————沒(méi)那么復(fù)雜,就直接再執(zhí)行的函數(shù)定義處上看就行了。把函數(shù)定義的作用域看成是函數(shù)執(zhí)行的作用域。這也是詞法作用域迷人的地方。

Show Me the Code

說(shuō)了那么多,有代碼才是王道,畢竟“Talk is cheap”。

“面向?qū)ο蟆币话愕木幊蹋簩?shí)現(xiàn)封裝

這段代碼來(lái)自MDN-用閉包模擬私有方法,有更改

var Counter = (function() {
  var privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }
  return {
    increment: function(dis) {
      changeBy(dis);
    },
    decrement: function(dis) {
      changeBy(-dis);
    },
    value: function() {
      return privateCounter;
    }
  }
})();

console.log(Counter.value()); // 0
Counter.increment(1);
Counter.increment(2);
console.log(Counter.value()); // 3
Counter.decrement(5);
console.log(Counter.value()); // -2

返回的是一個(gè)對(duì)象,這個(gè)對(duì)象有三個(gè)屬性,都是函數(shù)。而且這三個(gè)函數(shù)的scope屬性都是指向一個(gè)集合,這個(gè)集合包括外層匿名函數(shù)的的AO,和全局變量的VO。分析一下Counter.value()這個(gè)調(diào)用:value這個(gè)屬性對(duì)應(yīng)的匿名函數(shù)定義的時(shí)候,它的scope屬性確定,這個(gè)是詞法作用域的特性,這個(gè)scope屬性指向的是外部所有變量對(duì)象的集合(也就是上句說(shuō)的那個(gè)集合)。在最后調(diào)用Counter.value()的時(shí)候,創(chuàng)建先構(gòu)建作用域鏈,再創(chuàng)建執(zhí)行環(huán)境,再創(chuàng)建執(zhí)行環(huán)境的時(shí)候發(fā)現(xiàn)了一個(gè)變量標(biāo)識(shí)符privateCounter。好,接下來(lái)在函數(shù)體內(nèi)找找這個(gè)對(duì)應(yīng)的值,找不到;到外層的函數(shù),也就是那個(gè)Counter對(duì)應(yīng)的匿名函數(shù),誒找到了!好,將這個(gè)標(biāo)識(shí)符和這個(gè)量“關(guān)聯(lián)起來(lái)”。
結(jié)果,這樣下來(lái),返回的這個(gè)對(duì)象就類似于面向?qū)ο笞兂芍械摹巴獠拷涌凇?,而沒(méi)有被返回的那部分(也就是代碼中的var privateCounter function changeBy)則成了“私有的”,無(wú)法從外部直接訪問(wèn)。這樣的閉包模擬了數(shù)據(jù)的封裝和隱藏,一股熟悉而濃郁的C++味道襲來(lái)。當(dāng)然,這樣用的確不錯(cuò),但是關(guān)乎性能方面,MDN這樣推薦道:

如果不是因?yàn)槟承┨厥馊蝿?wù)而需要閉包,在沒(méi)有必要的情況下,在其它函數(shù)中創(chuàng)建函數(shù)是不明智的,因?yàn)殚]包對(duì)腳本性能具有負(fù)面影響,包括處理速度和內(nèi)存消耗。
例如,在創(chuàng)建新的對(duì)象或者類時(shí),方法通常應(yīng)該關(guān)聯(lián)于對(duì)象的原型,而不是定義到對(duì)象的構(gòu)造器中。原因是這將導(dǎo)致每次構(gòu)造器被調(diào)用,方法都會(huì)被重新賦值一次(也就是說(shuō),為每一個(gè)對(duì)象的創(chuàng)建)。

執(zhí)行環(huán)境到底是怎么建立的?

下面片段來(lái)自深入理解Javascript之執(zhí)行上下文(Execution Context)

  function foo(i) {
   var a = "hello";
   var b = function privateB() {

   };
   function c() {

   }
}

foo(22);

在調(diào)用foo(22)的時(shí)候,建立階段如下:

fooExecutionContext = {
   variableObject: {  // 變量對(duì)象
       arguments: {
           0: 22,
           length: 1
       },
       i: 22, // 形式參數(shù)聲明在函數(shù)聲明前
       c: pointer to function c() // 注意,函數(shù)聲明在變量聲明前
       a: undefined,
       b: undefined
   },
   // 作用鏈和變量對(duì)象順序問(wèn)題,有待鉆研,T.T
   // 在官方文檔中,貌似是作用域鏈先被創(chuàng)建(而且被稱作詞法環(huán)境組件)
   scopeChain: { ... },
   this: { ... }
}

由此可見(jiàn),在建立階段,除了arguments,函數(shù)的聲明,以及參數(shù)被賦予了具體的屬性值,其它的變量屬性默認(rèn)的都是undefined。一旦上述建立階段結(jié)束,引擎就會(huì)進(jìn)入代碼執(zhí)行階段,這個(gè)階段完成后,上述執(zhí)行上下文對(duì)象如下:

fooExecutionContext = {
   variableObject: {
       arguments: {
           0: 22,
           length: 1
       },
       i: 22,
       c: pointer to function c()
       a: "hello",
       b: pointer to function privateB()
   },
   scopeChain: { ... },
   this: { ... }
}

我們看到,只有在代碼執(zhí)行階段,變量屬性才會(huì)被賦予具體的值。

總結(jié)一下

分析代碼的時(shí)候,務(wù)必回看函數(shù)的定義,畢竟人家函數(shù)是一等貴族

記住函數(shù)作用域鏈 = (動(dòng))活動(dòng)對(duì)象(AO) + (靜)scope屬性。

執(zhí)行環(huán)境結(jié)構(gòu):

執(zhí)行環(huán)境創(chuàng)建后,才開(kāi)始執(zhí)行代碼,變量對(duì)象才開(kāi)始被賦值

變量提升 ==> 變量對(duì)象的創(chuàng)建

閉包 ===> 作用域鏈中靜態(tài)的部分,即scope屬性

官方文檔的補(bǔ)充


我的理解:詞法環(huán)境組件 ≈ 作用域;變量環(huán)境組件 ≈ 變量對(duì)象;

以初始化全局代碼的時(shí)候,貌似是創(chuàng)建變量對(duì)象在先。(這樣有什么特殊的意義嗎?)

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

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/79161.html

相關(guān)文章

  • js深入(三)作用鏈與閉包

    摘要:在之前我們根絕對(duì)象的原型說(shuō)過(guò)了的原型鏈,那么同樣的萬(wàn)物皆對(duì)象,函數(shù)也同樣存在這么一個(gè)鏈?zhǔn)降年P(guān)系,就是函數(shù)的作用域鏈作用域鏈?zhǔn)紫认葋?lái)回顧一下之前講到的原型鏈的尋找機(jī)制,就是實(shí)例會(huì)先從本身開(kāi)始找,沒(méi)有的話會(huì)一級(jí)一級(jí)的網(wǎng)上翻,直到頂端沒(méi)有就會(huì)報(bào)一 在之前我們根絕對(duì)象的原型說(shuō)過(guò)了js的原型鏈,那么同樣的js 萬(wàn)物皆對(duì)象,函數(shù)也同樣存在這么一個(gè)鏈?zhǔn)降年P(guān)系,就是函數(shù)的作用域鏈 作用域鏈 首先先來(lái)回...

    blair 評(píng)論0 收藏0
  • Javascript 函數(shù)、作用鏈與閉包

    摘要:而外層的函數(shù)不能訪問(wèn)內(nèi)層的變量或函數(shù),這樣的層層嵌套就形成了作用域鏈。閉包閉包是指有權(quán)訪問(wèn)另一個(gè)函數(shù)作用域中的變量的函數(shù),創(chuàng)建閉包的最常見(jiàn)的方式就是在一個(gè)函數(shù)內(nèi)創(chuàng)建另一個(gè)函數(shù),通過(guò)另一個(gè)函數(shù)訪問(wèn)這個(gè)函數(shù)的局部變量。 閉包是js中一個(gè)極為NB的武器,但也不折不扣的成了初學(xué)者的難點(diǎn)。因?yàn)閷W(xué)好閉包就要學(xué)好作用域,正確理解作用域鏈,然而想做到這一點(diǎn)就要深入的理解函數(shù),所以我們從函數(shù)說(shuō)起。 函數(shù)...

    ssshooter 評(píng)論0 收藏0
  • 前端基礎(chǔ)進(jìn)階(四):詳細(xì)圖解作用鏈與閉包

    摘要:之前一篇文章我們?cè)敿?xì)說(shuō)明了變量對(duì)象,而這里,我們將詳細(xì)說(shuō)明作用域鏈。而的作用域鏈,則同時(shí)包含了這三個(gè)變量對(duì)象,所以的執(zhí)行上下文可如下表示。下圖展示了閉包的作用域鏈。其中為當(dāng)前的函數(shù)調(diào)用棧,為當(dāng)前正在被執(zhí)行的函數(shù)的作用域鏈,為當(dāng)前的局部變量。 showImg(https://segmentfault.com/img/remote/1460000008329355);初學(xué)JavaScrip...

    aikin 評(píng)論0 收藏0
  • 「譯文」JavaScript核心

    摘要:在這個(gè)情況下我們可能需要使用構(gòu)造函數(shù),其以指定的模式來(lái)創(chuàng)造對(duì)象。構(gòu)造函數(shù)也有自己的,值為,也通過(guò)其屬性關(guān)聯(lián)到。從邏輯上來(lái)說(shuō),這是以棧的形式實(shí)現(xiàn)的,它叫作執(zhí)行上下文棧。 原文:http://dmitrysoshnikov.com/ecmascript/javascript-the-core/ 對(duì)象 原型鏈 構(gòu)造函數(shù) 執(zhí)行上下文棧 執(zhí)行上下文 變量對(duì)象 活動(dòng)對(duì)象 作用域鏈 閉包 Thi...

    高璐 評(píng)論0 收藏0
  • JS基礎(chǔ)——作用鏈與執(zhí)行環(huán)境

    摘要:函數(shù)的作用域會(huì)在函數(shù)執(zhí)行時(shí)用到,函數(shù)每次執(zhí)行都會(huì)創(chuàng)建一個(gè)執(zhí)行環(huán)境的內(nèi)部對(duì)象,每個(gè)執(zhí)行環(huán)境都有自己的作用域鏈。假設(shè)執(zhí)行,其對(duì)應(yīng)的作用域鏈如下函數(shù)執(zhí)行過(guò)程中,變量的查找時(shí)從作用域頭部開(kāi)始查找,如果找到就是使用改變量的值。 每一個(gè)函數(shù)存在一個(gè)[[Scope]]內(nèi)部屬性,包含了一個(gè)函數(shù)被創(chuàng)建得作用域中對(duì)象得集合,這個(gè)集合為函數(shù)得作用域鏈。例如下面的全局函數(shù): fucntion add(num1...

    Yi_Zhi_Yu 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

閱讀需要支付1元查看
<