摘要:全局和上下文中的作用域鏈這里不一定很有趣,但必須要提示一下。全局上下文的作用域鏈僅包含全局對象。代碼的上下文與當前的調用上下文擁有同樣的作用域鏈。代碼執(zhí)行時對作用域鏈的影響在中,在代碼執(zhí)行階段有兩個聲明能修改作用域鏈。
1 定義
我們已經(jīng)知道一個執(zhí)行上下文中的數(shù)據(jù)(參數(shù),變量,函數(shù))作為屬性存儲在變量對象中。
也知道變量對象是在每次進入上下文是創(chuàng)建并填入初始值,值的更新出現(xiàn)在代碼執(zhí)行階段。
作用域鏈就是這些變量對象的鏈表。
讓我們看一下和作用域相關的上下文結構
VO是當前上下文的變量對象,重點是Scope屬性,Scope = VO+[[scope]]。其中[[scope]]為所有父上下文變量對象的鏈表。
activeExecutionContext = { VO: {...}, // or AO this: thisValue, Scope: [ // Scope chain // 所有變量對象的列表 // for identifiers lookup ] };
函數(shù)的生命周期分為創(chuàng)建和激活。
2 函數(shù)創(chuàng)建階段var x = 10; function foo() { var y = 20; alert(x + y); } foo(); // 30
此前,我們僅僅談到有關當前上下文的變量對象。這里,我們看到變量“y”在函數(shù)“foo”中定義(意味著它在foo上下文的AO中),但是變量“x”并未在“foo”上下文中定義,相應地,它也不會添加到“foo”的AO中。乍一看,變量“x”相對于函數(shù)“foo”根本就不存在;但正如我們在下面看到的——也僅僅是“一瞥”,我們發(fā)現(xiàn),“foo”上下文的活動對象中僅包含一個屬性--“y”。
fooContext.AO = { y: undefined // undefined – 進入上下文的時候是20 – at activation };
函數(shù)“foo”如何訪問到變量“x”?理論上函數(shù)應該能訪問一個更高一層上下文的變量對象。實際上它正是這樣,這種機制是通過函數(shù)內部的[[scope]]屬性來實現(xiàn)的。
[[scope]]是所有父變量對象的層級鏈,處于當前函數(shù)上下文之上,在函數(shù)創(chuàng)建時存于其中。
注意這重要的一點--[[scope]]在函數(shù)創(chuàng)建時被存儲--靜態(tài)(不變的),永遠永遠,直至函數(shù)銷毀。即:函數(shù)可以永不調用,但[[scope]]屬性已經(jīng)寫入,并存儲在函數(shù)對象中。
3 函數(shù)激活階段正如在定義中說到的,進入上下文創(chuàng)建AO/VO之后,上下文的Scope屬性(變量查找的一個作用域鏈)作如下定義:
Scope = AO|VO + [[Scope]]
上面代碼的意思是:活動對象是作用域數(shù)組的第一個對象,即添加到作用域的前端。
Scope = [AO].concat([[Scope]]);
這個特點對于標示符解析的處理來說很重要。
標示符解析是一個處理過程,用來確定一個變量(或函數(shù)聲明)屬于哪個變量對象。
標識符解析過程包含與變量名對應屬性的查找,即作用域中變量對象的連續(xù)查找,從最深的上下文開始,繞過作用域鏈直到最上層。
這樣一來,在向上查找中,一個上下文中的局部變量較之于父作用域的變量擁有較高的優(yōu)先級。萬一兩個變量有相同的名稱但來自不同的作用域,那么第一個被發(fā)現(xiàn)的是在最深作用域中。
4 閉包在ECMAScript中,閉包與函數(shù)的[[scope]]直接相關,正如我們提到的那樣,[[scope]]在函數(shù)創(chuàng)建時被存儲,與函數(shù)共存亡。實際上,閉包是函數(shù)代碼和其[[scope]]的結合。
因為閉包函數(shù)在創(chuàng)建的時候就創(chuàng)建了父級的變量對象鏈表,也就是父級作用域鏈, 然后閉包函數(shù)再訪問父級作用域鏈中的變量,導致父級函數(shù)執(zhí)行完畢后仍然不能釋放執(zhí)行上下文的情況。
5 通過構造函數(shù)創(chuàng)建的函數(shù)的[[scope]]在上面的例子中,我們看到,在函數(shù)創(chuàng)建時獲得函數(shù)的[[scope]]屬性,通過該屬性訪問到所有父上下文的變量。但是,這個規(guī)則有一個重要的例外,它涉及到通過函數(shù)構造函數(shù)創(chuàng)建的函數(shù)。
var x = 10; function foo() { var y = 20; function barFD() { // 函數(shù)聲明 alert(x); alert(y); } var barFE = function () { // 函數(shù)表達式 alert(x); alert(y); }; var barFn = Function("alert(x); alert(y);"); barFD(); // 10, 20 barFE(); // 10, 20 barFn(); // 10, "y" is not defined } foo();
我們看到,通過函數(shù)構造函數(shù)(Function constructor)創(chuàng)建的函數(shù)“bar”,是不能訪問變量“y”的。但這并不意味著函數(shù)“barFn”沒有[[scope]]屬性(否則它不能訪問到變量“x”)。問題在于通過函構造函數(shù)創(chuàng)建的函數(shù)的[[scope]]屬性總是唯一的全局對象??紤]到這一點,如通過這種函數(shù)創(chuàng)建除全局之外的最上層的上下文閉包是不可能的。
6 全局和eval上下文中的作用域鏈這里不一定很有趣,但必須要提示一下。全局上下文的作用域鏈僅包含全局對象。代碼eval的上下文與當前的調用上下文(calling context)擁有同樣的作用域鏈。
globalContext.Scope = [ Global ]; evalContext.Scope === callingContext.Scope;7 代碼執(zhí)行時對作用域鏈的影響
在ECMAScript 中,在代碼執(zhí)行階段有兩個聲明能修改作用域鏈。這就是with聲明和catch語句。它們添加到作用域鏈的最前端,對象須在這些聲明中出現(xiàn)的標識符中查找。如果發(fā)生其中的一個,作用域鏈簡要的作如下修改:
Scope = withObject|catchObject + AO|VO + [[Scope]]
eval代碼運行的字符串利用當前調用的上下文,并且能夠修改當前上下文中的變量對象,也就是說eval內和eval外的代碼一樣,只是不能變量提升,執(zhí)行起來是一樣的。
with代碼塊和catch代碼塊都改變了作用域鏈,但是在他們代碼塊中聲明的變量,也存在了函數(shù)作用域中,只是with的對象和catch對象外部不能訪問而已。
eval("var x=3"); console.log(x); //3 with (obj1) { var innner ="inner"; console.log(a); //1 console.log(b); //2 } console.log(innner); // inner 仍能訪問 console.log(a) //訪問不到 try { throw new Error("error") } catch (e) { var cat = 2; } console.log(cat); //2 仍能訪問
英文原文:http://dmitrysoshnikov.com/ec...
本文絕大部分內容來自上述地址,僅做少許修改
文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://systransis.cn/yun/86609.html
摘要:代碼執(zhí)行階段在這個階段會生成三個重要的東西變量對象,作用域鏈變量對象在函數(shù)上下文中,我們用活動對象來表示變量對象。這時候執(zhí)行上下文的作用域鏈,我們命名為至此,作用域鏈創(chuàng)建完畢。 好久沒更新文章了,這一憋就是一個大的。說起js中的概念,執(zhí)行上下文和作用域應該是大家最容易混淆的,你說混淆就混淆吧,其實大多數(shù)人在開發(fā)的時候不是很關注這兩個名詞,但是這里面偏偏還夾雜好多其他的概念--變量提升啊...
摘要:之前一篇文章我們詳細說明了變量對象,而這里,我們將詳細說明作用域鏈。而的作用域鏈,則同時包含了這三個變量對象,所以的執(zhí)行上下文可如下表示。下圖展示了閉包的作用域鏈。其中為當前的函數(shù)調用棧,為當前正在被執(zhí)行的函數(shù)的作用域鏈,為當前的局部變量。 showImg(https://segmentfault.com/img/remote/1460000008329355);初學JavaScrip...
摘要:最后重點理解結論箭頭函數(shù)的,總是指向定義時所在的對象,而不是運行時所在的對象。輸出,箭頭函數(shù)不會綁定所以傳入指向無效。原因是,要徹底理解應該是建立在已經(jīng)大致理解了中的執(zhí)行上下文,作用域作用域鏈,閉包,變量對象,函數(shù)執(zhí)行過程的基礎上。 本文共 2025 字,看完只需 8 分鐘 概述 前面的文章講解了 JavaScript 中的執(zhí)行上下文,作用域,變量對象,this 的相關原理,但是我...
摘要:理解了這句話,我們就可以來看閉包了閉包前面說過,函數(shù)可以訪問函數(shù)作用域鏈中的變量,但如果我們想在函數(shù)外訪問函數(shù)內卻不行了。 不管是閉包還是this關鍵字,都是困擾JS初學者的比較難懂的東西,如果你對它們的認識還不足夠清晰,那么現(xiàn)在就一起把它們掌握掉。還是那句話,我們從最基本的開始,建立起一個非常清晰的知識結構,好了,開始吧 ? 閉包 當然我們今天說的是javascript里的閉包。要學...
摘要:理解作用域高級程序設計中有說到對象是在運行時基于函數(shù)的執(zhí)行環(huán)境綁定的在全局函數(shù)中,等于,而當函數(shù)被作為某個對象調用時,等于那個對象。指向與匿名函數(shù)沒有關系如果函數(shù)獨立調用,那么該函數(shù)內部的,則指向。 理解this作用域 《javascript高級程序設計》中有說到: this對象是在運行時基于函數(shù)的執(zhí)行環(huán)境綁定的:在全局函數(shù)中,this等于window,而當函數(shù)被作為某個對象調用時,t...
閱讀 686·2023-04-26 01:53
閱讀 2799·2021-11-17 17:00
閱讀 2941·2021-09-04 16:40
閱讀 2031·2021-09-02 15:41
閱讀 893·2019-08-26 11:34
閱讀 1273·2019-08-26 10:16
閱讀 1388·2019-08-23 17:51
閱讀 889·2019-08-23 16:50