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

資訊專欄INFORMATION COLUMN

ES規范解讀之作用域

周國輝 / 3291人閱讀

摘要:作用域鏈,它在解釋器進入到一個執行環境時初始化完成并將其分配給當前執行環境。每個執行環境的作用域鏈由當前環境的變量對象及父級環境的作用域鏈構成。即函數的變量對象被壓入其作用域鏈,此時至此的作用域鏈構建完成。

一道js面試題引發的思考

原文寫于 2015-02-11 原文鏈接

前陣子幫部門面試一前端,看了下面試題(年輕的時候寫后端java所以沒做過前端試題),其中有一道題是這樣的

比較下面兩段代碼,試述兩段代碼的不同之處
// A--------------------------
var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f();
}
checkscope();
 
// B---------------------------
var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f;
}
checkscope()();

首先A、B兩段代碼輸出返回的都是 "local scope",如果對這一點還有疑問的同學請自覺回去溫習一下js作用域的相關知識。。
那么既然輸出一樣那這兩段代碼具體的差異在哪呢?大部分人會說執行環境和作用域不一樣,但根本上是哪里不一樣就不是人人都能說清楚了。前陣子就這個問題重新翻了下js基礎跟ecmascript標準,如果我們想要刨根問底給出標準答案,那么我們需要先理解下面幾個概念:

變量對象(variable object)

原文:Every execution context has associated with it a variable object. Variables and functions declared in the source text are added as properties of the variable object. For function code, parameters are added as properties of the variable object.

簡言之就是:每一個執行上下文都會分配一個變量對象(variable object),變量對象的屬性由 變量(variable) 和 函數聲明(function declaration) 構成。在函數上下文情況下,參數列表(parameter list)也會被加入到變量對象(variable object)中作為屬性。變量對象與當前作用域息息相關。不同作用域的變量對象互不相同,它保存了當前作用域的所有函數和變量。

這里有一點特殊就是只有 函數聲明(function declaration) 會被加入到變量對象中,而 函數表達式(function expression)則不會。看代碼:

// 函數聲明
function a(){}
console.log(typeof a); // "function"
 
// 函數表達式
var a = function _a(){};
console.log(typeof a); // "function"
console.log(typeof _a); // "undefined"

函數聲明的方式下,a會被加入到變量對象中,故當前作用域能打印出 a。
函數表達式情況下,a作為變量會加入到變量對象中,_a作為函數表達式則不會加入,故 a 在當前作用域能被正確找到,_a則不會。

另外,關于變量如何初始化,看這里

關于Global Object
當js編譯器開始執行的時候會初始化一個Global Object用于關聯全局的作用域。對于全局環境而言,global object就是變量對象(variable object)。變量對象對于程序而言是不可讀的,只有編譯器才有權訪問變量對象。在瀏覽器端,global object被具象成window對象,也就是說 global object === window === 全局環境的variable object。因此global object對于程序而言也是唯一可讀的variable object。

活動對象(activation object)

原文:When control enters an execution context for function code, an object called the activation object is created and associated with the execution context. The activation object is initialised with a property with name arguments and attributes { DontDelete }. The initial value of this property is the arguments object described below.

The activation object is then used as the variable object for the purposes of variable instantiation.

簡言之:當函數被激活,那么一個活動對象(activation object)就會被創建并且分配給執行上下文。活動對象由特殊對象 arguments 初始化而成。隨后,他被當做變量對象(variable object)用于變量初始化。
用代碼來說明就是:

function a(name, age){
    var gender = "male";
    function b(){}
}
a(“k”,10);

a被調用時,在a的執行上下文會創建一個活動對象AO,并且被初始化為 AO = [arguments]。隨后AO又被當做變量對象(variable object)VO進行變量初始化,此時 VO = [arguments].contact([name,age,gender,b])。

執行環境和作用域鏈(execution context and scope chain)

execution context

顧名思義 執行環境/執行上下文。在javascript中,執行環境可以抽象的理解為一個object,它由以下幾個屬性構成:  
executionContext:{
    variable object:vars,functions,arguments,
    scope chain: variable object + all parents scopes
    thisValue: context object
}

此外在js解釋器運行階段還會維護一個環境棧,當執行流進入一個函數時,函數的環境就會被壓入環境棧,當函數執行完后會將其環境彈出,并將控制權返回前一個執行環境。環境棧的頂端始終是當前正在執行的環境。  

scope chain
作用域鏈,它在解釋器進入到一個執行環境時初始化完成并將其分配給當前執行環境。每個執行環境的作用域鏈由當前環境的變量對象及父級環境的作用域鏈構成。
作用域鏈具體是如何構建起來的呢,先上代碼:

function test(num){
    var a = "2";
    return a+num;
}
test(1);

執行流開始 初始化function test,test函數會維護一個私有屬性 [[scope]],并使用當前環境的作用域鏈初始化,在這里就是 test.[[Scope]]=global scope.

test函數執行,這時候會為test函數創建一個執行環境,然后通過復制函數的[[Scope]]屬性構建起test函數的作用域鏈。此時 test.scopeChain = [test.[[Scope]]]

test函數的活動對象被初始化,隨后活動對象被當做變量對象用于初始化。即 test.variableObject = test.activationObject.contact[num,a] = [arguments].contact[num,a]

test函數的變量對象被壓入其作用域鏈,此時 test.scopeChain = [ test.variableObject, test.[[scope]]];

至此test的作用域鏈構建完成。

說了這么多概念,回到面試題上,返回結果相同那么A、B兩段代碼究竟不同在哪里,個人覺得標準答案在這里:

答案來了

首先是A:

進入全局環境上下文,全局環境被壓入環境棧,contextStack = [globalContext]

全局上下文環境初始化,

globalContext={
    variable object:[scope, checkscope],
    scope chain: variable object // 全局作用域鏈
}
,同時checkscope函數被創建,此時 checkscope.[[Scope]] = globalContext.scopeChain

執行checkscope函數,進入checkscope函數上下文,checkscope被壓入環境棧,contextStack=[checkscopeContext, globalContext]。隨后checkscope上下文被初始化,它會復制checkscope函數的[[Scope]]變量構建作用域,即 checkscopeContext={ scopeChain : [checkscope.[[Scope]]] }

checkscope的活動對象被創建 此時 checkscope.activationObject = [arguments], 隨后活動對象被當做變量對象用于初始化,checkscope.variableObject = checkscope.activationObject = [arguments, scope, f],隨后變量對象被壓入checkscope作用域鏈前端,(checckscope.scopeChain = [checkscope.variableObject, checkscope.[[Scope]] ]) == [[arguments, scope, f], globalContext.scopeChain]

函數f被初始化,f.[[Scope]] = checkscope.scopeChain。

checkscope執行流繼續往下走到 return f(),進入函數f執行上下文。函數f執行上下文被壓入環境棧,contextStack = [fContext, checkscopeContext, globalContext]。函數f重復 第4步 動作。最后 f.scopeChain = [f.variableObject,checkscope.scopeChain]

函數f執行完畢,f的上下文從環境棧中彈出,此時 contextStack = [checkscopeContext, globalContext]。同時返回 scope, 解釋器根據f.scopeChain查找變量scope,在checkscope.scopeChain中找到scope(local scope)。

checkscope函數執行完畢,其上下文從環境棧中彈出,contextStack = [globalContext]

如果你理解了A的執行流程,那么B的流程在細節上一致,唯一的區別在于B的環境棧變化不一樣,

A: contextStack = [globalContext] ---> contextStack = [checkscopeContext, globalContext] ---> contextStack = [fContext, checkscopeContext, globalContext] ---> contextStack = [checkscopeContext, globalContext] ---> contextStack = [globalContext]

B: contextStack = [globalContext] ---> contextStack = [checkscopeContext, globalContext] ---> contextStack = [fContext, globalContext] ---> contextStack = [globalContext]

也就是說,真要說這兩段代碼有啥不同,那就是他們執行過程中環境棧的變化不一樣,其他的兩種方式都一樣。

其實對于理解這兩段代碼而言最根本的一點在于,javascript是使用靜態作用域的語言,他的作用域在函數創建的時候便已經確定(不含arguments)。

說了這么一大坨偏理論的東西,能堅持看下來的同學估計都要睡著了...是的,這么一套理論性的東西糾結有什么用呢,我只要知道函數作用域在創建時便已經生成不就好了么。沒有實踐價值的理論往往得不到重視。那我們來看看,當我們了解到這一套理論之后我們的世界到底會發生了什么變化:

這樣一段代碼

function setFirstName(firstName){
     
    return function(lastName){
        return firstName+" "+lastName;
    }
}
 
var setLastName = setFirstName("kuitos");
var name = setLastName("lau");
 
 
// 乍看之下這段代碼沒有任何問題,但是世界就是這樣,大部分東西都禁不起考究(我認真起來連自己都害怕哈哈哈哈)。。
// 調用setFirstName函數時返回一個匿名函數,該匿名函數會持有setFirstName函數作用域的變量對象(里面包含arguments和firstName),不管匿名函數是否會使用該變量對象里的信息,這個持有邏輯均不會改變。
// 也就是當setFirstName函數執行完之后其執行環境被銷毀,但是他的變量對象會一直保存在內存中不被銷毀(因為被匿名函數hold)。同樣的,垃圾回收機制會因為變量對象被一直hold而不做回收處理。這個時候內存泄露就發生了。這時候我們需要做手動釋放內存的處理。like this:
setLastName = null;
// 由于匿名函數的引用被置為null,那么其hold的setFirstName的活動對象就能被安全回收了。
// 當然,現代瀏覽器引擎(以V8為首)都會嘗試回收閉包所占用的內存,所以這一點我們也不必過多處理。

ps:最后,關于閉包引起的內存泄露那都是因為瀏覽器的gc問題(IE8以下為首)導致的,跟js本身沒有關系,所以,請不要再問js閉包會不會引發內存泄露了,謝謝合作!

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/86005.html

相關文章

  • ES規范解讀賦值操作符&屬性訪問器

    摘要:那么什么是基礎對象組件呢,舉兩個例子我們再來看看屬性訪問器,就是括號操作符及點號操作符都做了什么屬性訪問器也就是說括號跟點號對解釋器而言是一樣的。 ES規范解讀之賦值操作符&屬性訪問器 原文:https://github.com/kuitos/kuitos.github.io/issues/24事情起源于某天某妹子同事在看angular文檔中關于Scope的說明Understandin...

    funnyZhang 評論0 收藏0
  • JavaScript深入系列15篇正式完結!

    摘要:寫在前面深入系列共計篇已經正式完結,這是一個旨在幫助大家,其實也是幫助自己捋順底層知識的系列。深入系列自月日發布第一篇文章,到月日發布最后一篇,感謝各位朋友的收藏點贊,鼓勵指正。 寫在前面 JavaScript 深入系列共計 15 篇已經正式完結,這是一個旨在幫助大家,其實也是幫助自己捋順 JavaScript 底層知識的系列。重點講解了如原型、作用域、執行上下文、變量對象、this、...

    fxp 評論0 收藏0
  • JavaScript深入從ECMAScript規范解讀this

    摘要:深入系列第六篇,本篇我們追根溯源,從規范解讀在函數調用時到底是如何確定的。因為我們要從規范開始講起。規范類型包括和。下一篇文章深入之執行上下文深入系列深入系列目錄地址。如果有錯誤或者不嚴謹的地方,請務必給予指正,十分感謝。 JavaScript深入系列第六篇,本篇我們追根溯源,從 ECMAScript5 規范解讀 this 在函數調用時到底是如何確定的。 前言 在《JavaScript...

    TIGERB 評論0 收藏0
  • 由 ECMA 規范解讀 Javascript 可執行上下文概念

    摘要:不包括作為其嵌套函數的被解析的源代碼。作用域鏈當代碼在一個環境中執行時,會創建變量對象的一個作用域鏈。棧結構最頂層的執行環境稱為當前運行的執行環境,最底層是全局執行環境。無限制函數上下文。或者拋出異常退出一個執行環境。 前言 其實規范這東西不是給人看的,它更多的是給語言實現者提供參考。但是當碰到問題找不到答案時,規范往往能提供想要的答案 。偶爾讀一下能夠帶來很大的啟發和思考,如果只讀一...

    daryl 評論0 收藏0
  • JavaScript深入執行上下文

    摘要:深入系列第七篇,結合之前所講的四篇文章,以權威指南的為例,具體講解當函數執行的時候,執行上下文棧變量對象作用域鏈是如何變化的。前言在深入之執行上下文棧中講到,當代碼執行一段可執行代碼時,會創建對應的執行上下文。 JavaScript深入系列第七篇,結合之前所講的四篇文章,以權威指南的demo為例,具體講解當函數執行的時候,執行上下文棧、變量對象、作用域鏈是如何變化的。 前言 在《Jav...

    gougoujiang 評論0 收藏0

發表評論

0條評論

周國輝

|高級講師

TA的文章

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