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

資訊專欄INFORMATION COLUMN

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

周國(guó)輝 / 3311人閱讀

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

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

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

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

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

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

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

這里有一點(diǎn)特殊就是只有 函數(shù)聲明(function declaration) 會(huì)被加入到變量對(duì)象中,而 函數(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ì)被加入到變量對(duì)象中,故當(dāng)前作用域能打印出 a。
函數(shù)表達(dá)式情況下,a作為變量會(huì)加入到變量對(duì)象中,_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)全局的作用域。對(duì)于全局環(huán)境而言,global object就是變量對(duì)象(variable object)。變量對(duì)象對(duì)于程序而言是不可讀的,只有編譯器才有權(quán)訪問(wèn)變量對(duì)象。在瀏覽器端,global object被具象成window對(duì)象,也就是說(shuō) global object === window === 全局環(huán)境的variable object。因此global object對(duì)于程序而言也是唯一可讀的variable object。

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

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

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

a被調(diào)用時(shí),在a的執(zhí)行上下文會(huì)創(chuàng)建一個(gè)活動(dòng)對(duì)象AO,并且被初始化為 AO = [arguments]。隨后AO又被當(dāng)做變量對(duì)象(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)境的變量對(duì)象及父級(jí)環(huán)境的作用域鏈構(gòu)成。
作用域鏈具體是如何構(gòu)建起來(lái)的呢,先上代碼:

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)境,然后通過(guò)復(fù)制函數(shù)的[[Scope]]屬性構(gòu)建起test函數(shù)的作用域鏈。此時(shí) test.scopeChain = [test.[[Scope]]]

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

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

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

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

答案來(lái)了

首先是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)對(duì)象被創(chuàng)建 此時(shí) checkscope.activationObject = [arguments], 隨后活動(dòng)對(duì)象被當(dāng)做變量對(duì)象用于初始化,checkscope.variableObject = checkscope.activationObject = [arguments, scope, f],隨后變量對(duì)象被壓入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]

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

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

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

這樣一段代碼

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

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

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

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

相關(guān)文章

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

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

    funnyZhang 評(píng)論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í)行上下文、變量對(duì)象、this、...

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

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

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

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

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

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

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

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

0條評(píng)論

周國(guó)輝

|高級(jí)講師

TA的文章

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