亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

ES規(guī)范解讀之作用域

周國輝 / 3383人閱讀

摘要:作用域鏈,它在解釋器進(jìn)入到一個(gè)執(zhí)行環(huán)境時(shí)初始化完成并將其分配給當(dāng)前執(zhí)行環(huán)境。每個(gè)執(zhí)行環(huán)境的作用域鏈由當(dāng)前環(huán)境的變量對象及父級(jí)環(huán)境的作用域鏈構(gòu)成。即函數(shù)的變量對象被壓入其作用域鏈,此時(shí)至此的作用域鏈構(gòu)建完成。

一道js面試題引發(fā)的思考

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

前陣子幫部門面試一前端,看了下面試題(年輕的時(shí)候?qū)懞蠖薺ava所以沒做過前端試題),其中有一道題是這樣的

比較下面兩段代碼,試述兩段代碼的不同之處
// 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",如果對這一點(diǎn)還有疑問的同學(xué)請自覺回去溫習(xí)一下js作用域的相關(guān)知識(shí)。。
那么既然輸出一樣那這兩段代碼具體的差異在哪呢?大部分人會(huì)說執(zhí)行環(huán)境和作用域不一樣,但根本上是哪里不一樣就不是人人都能說清楚了。前陣子就這個(gè)問題重新翻了下js基礎(chǔ)跟ecmascript標(biāo)準(zhǔn),如果我們想要刨根問底給出標(biāo)準(zhǔn)答案,那么我們需要先理解下面幾個(gè)概念:

變量對象(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.

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

這里有一點(diǎn)特殊就是只有 函數(shù)聲明(function declaration) 會(huì)被加入到變量對象中,而 函數(shù)表達(dá)式(function expression)則不會(huì)??创a:

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

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

另外,關(guān)于變量如何初始化,看這里

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

活動(dòng)對象(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.

簡言之:當(dāng)函數(shù)被激活,那么一個(gè)活動(dòng)對象(activation object)就會(huì)被創(chuàng)建并且分配給執(zhí)行上下文?;顒?dòng)對象由特殊對象 arguments 初始化而成。隨后,他被當(dāng)做變量對象(variable object)用于變量初始化。
用代碼來說明就是:

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

a被調(diào)用時(shí),在a的執(zhí)行上下文會(huì)創(chuàng)建一個(gè)活動(dòng)對象AO,并且被初始化為 AO = [arguments]。隨后AO又被當(dāng)做變量對象(variable object)VO進(jìn)行變量初始化,此時(shí) VO = [arguments].contact([name,age,gender,b])。

執(zhí)行環(huán)境和作用域鏈(execution context and scope chain)

execution context

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

此外在js解釋器運(yùn)行階段還會(huì)維護(hù)一個(gè)環(huán)境棧,當(dāng)執(zhí)行流進(jìn)入一個(gè)函數(shù)時(shí),函數(shù)的環(huán)境就會(huì)被壓入環(huán)境棧,當(dāng)函數(shù)執(zhí)行完后會(huì)將其環(huán)境彈出,并將控制權(quán)返回前一個(gè)執(zhí)行環(huán)境。環(huán)境棧的頂端始終是當(dāng)前正在執(zhí)行的環(huán)境。  

scope chain
作用域鏈,它在解釋器進(jìn)入到一個(gè)執(zhí)行環(huán)境時(shí)初始化完成并將其分配給當(dāng)前執(zhí)行環(huán)境。每個(gè)執(zhí)行環(huán)境的作用域鏈由當(dāng)前環(huán)境的變量對象及父級(jí)環(huán)境的作用域鏈構(gòu)成。
作用域鏈具體是如何構(gòu)建起來的呢,先上代碼:

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

執(zhí)行流開始 初始化function test,test函數(shù)會(huì)維護(hù)一個(gè)私有屬性 [[scope]],并使用當(dāng)前環(huán)境的作用域鏈初始化,在這里就是 test.[[Scope]]=global scope.

test函數(shù)執(zhí)行,這時(shí)候會(huì)為test函數(shù)創(chuàng)建一個(gè)執(zhí)行環(huán)境,然后通過復(fù)制函數(shù)的[[Scope]]屬性構(gòu)建起test函數(shù)的作用域鏈。此時(shí) test.scopeChain = [test.[[Scope]]]

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

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

至此test的作用域鏈構(gòu)建完成。

說了這么多概念,回到面試題上,返回結(jié)果相同那么A、B兩段代碼究竟不同在哪里,個(gè)人覺得標(biāo)準(zhǔn)答案在這里:

答案來了

首先是A:

進(jìn)入全局環(huán)境上下文,全局環(huán)境被壓入環(huán)境棧,contextStack = [globalContext]

全局上下文環(huán)境初始化,

globalContext={
    variable object:[scope, checkscope],
    scope chain: variable object // 全局作用域鏈
}
,同時(shí)checkscope函數(shù)被創(chuàng)建,此時(shí) checkscope.[[Scope]] = globalContext.scopeChain

執(zhí)行checkscope函數(shù),進(jìn)入checkscope函數(shù)上下文,checkscope被壓入環(huán)境棧,contextStack=[checkscopeContext, globalContext]。隨后checkscope上下文被初始化,它會(huì)復(fù)制checkscope函數(shù)的[[Scope]]變量構(gòu)建作用域,即 checkscopeContext={ scopeChain : [checkscope.[[Scope]]] }

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

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

checkscope執(zhí)行流繼續(xù)往下走到 return f(),進(jìn)入函數(shù)f執(zhí)行上下文。函數(shù)f執(zhí)行上下文被壓入環(huán)境棧,contextStack = [fContext, checkscopeContext, globalContext]。函數(shù)f重復(fù) 第4步 動(dòng)作。最后 f.scopeChain = [f.variableObject,checkscope.scopeChain]

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

checkscope函數(shù)執(zhí)行完畢,其上下文從環(huán)境棧中彈出,contextStack = [globalContext]

如果你理解了A的執(zhí)行流程,那么B的流程在細(xì)節(jié)上一致,唯一的區(qū)別在于B的環(huán)境棧變化不一樣,

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]

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

其實(shí)對于理解這兩段代碼而言最根本的一點(diǎn)在于,javascript是使用靜態(tài)作用域的語言,他的作用域在函數(shù)創(chuàng)建的時(shí)候便已經(jīng)確定(不含arguments)。

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

這樣一段代碼

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

ps:最后,關(guān)于閉包引起的內(nèi)存泄露那都是因?yàn)闉g覽器的gc問題(IE8以下為首)導(dǎo)致的,跟js本身沒有關(guān)系,所以,請不要再問js閉包會(huì)不會(huì)引發(fā)內(nèi)存泄露了,謝謝合作!

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

轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/86005.html

相關(guān)文章

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

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

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

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

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

    摘要:深入系列第六篇,本篇我們追根溯源,從規(guī)范解讀在函數(shù)調(diào)用時(shí)到底是如何確定的。因?yàn)槲覀円獜囊?guī)范開始講起。規(guī)范類型包括和。下一篇文章深入之執(zhí)行上下文深入系列深入系列目錄地址。如果有錯(cuò)誤或者不嚴(yán)謹(jǐn)?shù)牡胤剑垊?wù)必給予指正,十分感謝。 JavaScript深入系列第六篇,本篇我們追根溯源,從 ECMAScript5 規(guī)范解讀 this 在函數(shù)調(diào)用時(shí)到底是如何確定的。 前言 在《JavaScript...

    TIGERB 評論0 收藏0
  • 由 ECMA 規(guī)范解讀 Javascript 可執(zhí)行上下文概念

    摘要:不包括作為其嵌套函數(shù)的被解析的源代碼。作用域鏈當(dāng)代碼在一個(gè)環(huán)境中執(zhí)行時(shí),會(huì)創(chuàng)建變量對象的一個(gè)作用域鏈。棧結(jié)構(gòu)最頂層的執(zhí)行環(huán)境稱為當(dāng)前運(yùn)行的執(zhí)行環(huán)境,最底層是全局執(zhí)行環(huán)境。無限制函數(shù)上下文?;蛘邟伋霎惓M顺鲆粋€(gè)執(zhí)行環(huán)境。 前言 其實(shí)規(guī)范這東西不是給人看的,它更多的是給語言實(shí)現(xiàn)者提供參考。但是當(dāng)碰到問題找不到答案時(shí),規(guī)范往往能提供想要的答案 。偶爾讀一下能夠帶來很大的啟發(fā)和思考,如果只讀一...

    daryl 評論0 收藏0
  • JavaScript深入執(zhí)行上下文

    摘要:深入系列第七篇,結(jié)合之前所講的四篇文章,以權(quán)威指南的為例,具體講解當(dāng)函數(shù)執(zhí)行的時(shí)候,執(zhí)行上下文棧變量對象作用域鏈?zhǔn)侨绾巫兓摹G把栽谏钊胫畧?zhí)行上下文棧中講到,當(dāng)代碼執(zhí)行一段可執(zhí)行代碼時(shí),會(huì)創(chuàng)建對應(yīng)的執(zhí)行上下文。 JavaScript深入系列第七篇,結(jié)合之前所講的四篇文章,以權(quán)威指南的demo為例,具體講解當(dāng)函數(shù)執(zhí)行的時(shí)候,執(zhí)行上下文棧、變量對象、作用域鏈?zhǔn)侨绾巫兓摹?前言 在《Jav...

    gougoujiang 評論0 收藏0

發(fā)表評論

0條評論

周國輝

|高級(jí)講師

TA的文章

閱讀更多
最新活動(dòng)
閱讀需要支付1元查看
<