摘要:首先,在創建函數時,作用域鏈內就會先填入對象,圖片只例舉了全部變量中的一部分。然后,解釋器進入函數的執行環境,同樣的,首先填入父級的作用域鏈,就是的,包括了對象活動對象。之后再把的活動對象填入到作用域鏈最頂部,這就是的作用域鏈了。
之前學習JS函數部分時,提到了作用域這一節,但是因為使用材料書不同,今天在讀博客的時候發現其實還有一個知識點即作用域鏈,所以來寫一些個人理解和認識加深記憶。
引用:JavaScript中的作用域鏈和閉包
JS的作用域和作用域鏈
結合代碼圖文講解JavaScript中的作用域與作用域鏈(主要)
先上考試代碼:
/==========例1========== var scope="global"; function fn(){ alert(scope); var scope="local"; alert(scope); } fn(); //輸出結果? alert(scope);//輸出結果? //===========例2========== var scope="global"; function fn(){ alert(scope); scope="local"; alert(scope); } fn(); //輸出結果? alert(scope);//輸出結果? //===========例3========= var scope="global"; function fn(scope){ alert(scope); scope="local"; alert(scope); } fn(); //輸出結果? alert(scope);//輸出結果?
我當時做時候,卡在了第三題,后面明白了。
例1:只要記得變量聲明提升,例1應該沒什么問題,由于var變量聲明提前,所以當調用fn()時,第一個alert應該彈出undefined,之后賦值,再alert出"local"。而函數外的alert之前調用全局變量scope,彈出"global"。
var scope = "global"; function fn() { var scope; alert(scope); // undefined scope = "local"; alert(scope); // local }; fn(); alert(scope); // global
例2:由于函數內沒有var聲明變量,所以函數內的scope指向的是全局變量scope,那alert當然是全局變量的值啦——"global",之后賦值,再次alert,彈出"local"。此時全局變量scope已經被重新賦值,所以函數外的alert彈出"local"。
例3:這里先不提,后面就OK了。當時我不知道如果傳入實參在函數內是以什么方式存在,不傳入值為多少等。
板書ing
作用域鏈:作用域鏈(Scope Chain)是javascript內部中一種變量、函數查找機制,它決定了變量和函數的作用范圍,即作用域,理解作用域鏈的作用原理,上一篇文章的三個例子也就能理解了,從而知其然也知其所以然。
作用域鏈是ECMAScript-262說明文檔中的概念,javascript引擎是按ECMAScript-262說明文檔去實現的,了解javascript引擎的工作原理有利于我們理解javascript的特性,但絕大多數js程序員不會去了解非常底層的技術,所以閱讀ECMAScript-262說明文檔,我們可以有一個直觀的方式去模擬javascript引擎的工作原理。
本文將通過1999年的ECMAScript-262-3th第三版來說明作用域鏈的形成原理,將會介紹執行環境,變量對象和活動對象,arguments對象,作用域鏈等幾個概念。2009年發布了ECMAScript-262-5th第五版,不同的是取消了變量對象和活動對象等概念,引入了詞法環境(Lexical Environments)、環境記錄(EnviromentRecord)等新的概念,所以兩個版本的概念不要混淆了。
重點來了!
1. 執行環境(Execution Contexts)執行環境(Execution Contexts)也被翻譯為執行上下文,當解析器進入ECMAScript的可執行代碼,解析器就進入一個執行環境,活動的執行環境組成一個邏輯上的棧,在這個邏輯棧頂部的執行環境是當前運行的執行環境。
注:ECMAScript中有三種可執行代碼,Global、Function和Eval,全局環境即是Global可執行代碼,函數即是Function可執行代碼。邏輯棧是一種特殊的數據存儲格式,特點是‘先進后出,后進先出",添加數據會先壓入邏輯棧頂部,刪除數據必須先從頂部開始刪除。
變量對象(Variable Object)、活動對象(Activation Object)和Arguments對象(Arguments Object)
(上面這句話很重要哦)
每個執行環境都有一個與之關聯的變量對象,當解析器進入執行環境時,就會創建一個變量對象,變量對象保存著在當前執行環境中聲明的變量和函數的引用。
變量對象是一個抽象的概念,在不同的執行環境中,變量對象有不同的身份,在解析器進入任何執行環境之前,就已經創建了一個Global對象,當解析器進入全局執行環境時,Global對象就充當變量對象,當解析器進入一個函數時,就會創建一個活動對象充當變量對象。
我的理解是:解析器在執行代碼時,會遇到不同的執行環境,此時,會創建一個變量對象,里面存放了環境內的變量和對象(函數)引用。
當執行環境是變量,則會生成一個Global對象,此時變量對象就是Global對象
當執行環境是函數,則會生成一個活動對象(Activation Object)
2.解析器處理代碼時的兩個階段我們都知道javascript解析器是一段一段解析處理代碼的,為毛?這就要涉及解析器處理代碼時的兩個階段,解析代碼和執行代碼。
當解析器進入執行環境時,變量對象就會添加執行環境中聲明的變量和函數作為它的屬性,這就意味著變量和函數在聲明之前已經可用,變量值為undefined,這就是變量和函數聲明提升(Hoisting)的原因,與此同時作用域鏈和this確定,此過程為解析階段,俗稱預解析。接著解析器開始執行代碼,為變量添加相應值的引用,得到執行結果,此過程為執行階段。
我們還是用栗子談吧
var a=123; var b="abc"; function c(){ alert("11"); }
記得之前那句話嗎?在解析器進入任何執行環境之前,就已經創建了一個Global對象,當解析器進入全局執行環境時,Global對象就充當變量對象。一開始,JavaScript解析器就已經生成了一個Global Object來充當變量對象,里面存放了全局環境里的變量,對象(函數)等。就如上圖所示了,所以這也是為什么我們在函數內部聲明變量時,聲名會提前,賦值undefined的原因了。到現在為止,執行到這就是預解析的過程也叫解析代碼。
然后開始執行賦值等操作,此過程就叫執行過程。
再看這個:
function testFn(a){ var b="123"; function c(){ alert("abc"); } } testFn(10);
解析器進入函數執行環境時,則會創建一個活動對象作為變量對象,活動對象還會創建一個Arguments對象,arguments對象是一個參數集合,用來保存參數,這就是我們寫函數時可以使用arguments[0]等來使用參數的原因。
var a="123"; function testFn(b){ var c="abc"; function testFn2(){ var d="efg"; alert(a); } testFn2(); } testFn(10);
首先,在創建函數testFn時,作用域鏈內([[scope]])就會先填入Global Object對象,圖片只例舉了全部變量中的一部分。
當解析器進入到testFn的執行環境(執行上下文)時,在將函數的活動對象添加到Global對象之前,注意是之前,形成一個作用域鏈。
然后,解釋器進入testFn2函數的執行環境,同樣的,首先填入父級的作用域鏈,就是testFn的[[scope]]],包括了Global對象、testFn活動對象。之后再把testFn2的活動對象填入到作用域鏈最頂部,這就是testFn2的作用域鏈了。
testFn2調用變量a時,首先在當前的testFn2活動對象中查找,如果沒有找到就順著作用域鏈向上,在testFn活動對象中查找變量a,如果沒有找到再順著作用域鏈向上查找,直到在最后Global對象中找到為止,否則報錯。所以函數內部可以調用外部環境的變量,外部環境不能調用函數內部的變量,這就是作用域特性的原理。大概總結一下:
執行環境:(Execution Contexts)也被翻譯為執行上下文,當解析器進入ECMAScript的可執行代碼,解析器就進入一個執行環境,活動的執行環境組成一個邏輯上的棧,在這個邏輯棧頂部的執行環境是當前運行的執行環境。
ECMAScript中有三種可執行代碼,Global、Function和Eval,全局環境即是Global可執行代碼,函數即是Function可執行代碼。邏輯棧是一種特殊的數據存儲格式,特點是‘先進后出,后進先出",添加數據會先壓入邏輯棧頂部,刪除數據必須先從頂部開始刪除。
變量對象(Variable Object)、活動對象(Activation Object)和Arguments對象(Arguments Object)
每個執行環境都有一個與之關聯的變量對象,當解析器進入執行環境時,就會創建一個變量對象,變量對象保存著在當前執行環境中聲明的變量和函數的引用。
變量對象是一個抽象的概念,在不同的執行環境中,變量對象有不同的身份,在解析器進入任何執行環境之前,就已經創建了一個Global對象,當解析器進入全局執行環境時,Global對象就充當變量對象,當解析器進入一個函數時,就會創建一個活動對象(Activation Object)充當變量對象。
大致過程: 1. 自動創建Global對象(當解析器進入全局執行環境時,調用變量和函數時只在Global對象中查找。)
2. 解釋器進入執行環境(執行上下文)(也可理解為執行函數時等等。)
3.生成變量對象(每個執行環境都有一個與之關聯的變量對象,當解析器進入執行環境時,就會創建一個變量對象,變量對象保存著在當前執行環境中聲明的變量和函數的引用。)
(變量對象是一個抽象的概念,在不同的執行環境中,變量對象有不同的身身份。)
4. 創建作用域鏈(執行過程中的預解析、執行階段)(每個執行環境都有一個與之關聯的作用域鏈,當解析器進入執行環境時被定義,作用域鏈是一個對象列表,用來檢索各個變量對象中的變量和函數,這樣可以保證執行環境有權訪問哪些變量和函數)
(解析階段:當解析器進入執行環境時,變量對象就會添加執行環境中聲明的變量和函數作為它的屬性,這就意味著變量和函數在聲明之前已經可用,變量值為undefined,這就是變量和函數聲明提升(Hoisting)的原因,與此同時作用域鏈和this確定,此過程為解析階段,俗稱預解析。
執行階段:接著解析器開始執行代碼,為變量添加相應值的引用,得到執行結果,此過程為執行階段。)
這里也就是說,變量對象先于作用域鏈創建前就以生成完畢?
5.按優先級填入Global對象、活動對象等 6. 整個作用域鏈創建完成我們回到最初的題目,最后的例3中,調用fn時,并沒有傳參,所以fn函數的活動對象中沒有相關的鍵值(注意只是沒有值,但存在這個屬性),第一個alert彈出undefined,之后為其賦值,這時fn函數的活動對象中的scope就有值了,之后alert調用,搜索時自然先從優先級最高的fn活動對象中尋找,然后彈出"local"。
而函數外的alert,依舊只有Global對象,其中的值未曾改變,所以彈出"global"
// undefined local global
了解作用域和作用域鏈都更好的幫助了解閉包噢。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/83310.html
摘要:至此作用域鏈創建完畢。好了,通過深入理解作用域鏈,我們能跟好的理解的運行機制和閉包的原理。 前言 理解javascript中的作用域和作用域鏈對我們理解js這們語言。這次想深入的聊下關于js執行的內部機制,主要討論下,作用域,作用域鏈,閉包的概念。為了更好的理解這些東西,我模擬了當一個函數執行時,js引擎做了哪些事情--那些我們看不見的動作。 關鍵詞: 執行環境 作用域 作用域鏈 變...
摘要:開篇作用域是每種計算機語言最重要的基礎之一,因此要想深入的學習作用域和作用域鏈就是個繞不開的話題。這樣由多個執行上下文的變量對象構成的鏈表就叫做作用域鏈。這時候執行上下文的作用域鏈,我們命名為至此,作用域鏈創建完畢。 開篇 作用域是每種計算機語言最重要的基礎之一,因此要想深入的學習JavaScript,作用域和作用域鏈就是個繞不開的話題。 在《深入學習js之—-執行上下文棧》中我們提到...
摘要:全局執行環境的變量對象始終是作用域鏈中的最后一個變量對象。綜上,每個函數對應一個執行環境,每個執行環境對應一個變量對象,而多個變量對象構成了作用域鏈,如果當前執行環境是函數,那么其活動對象在作用域鏈的前端。 1.幾個概念 先說幾個概念:函數、執行環境、變量對象、作用域鏈、活動對象。這幾個東東之間有什么關系呢,往下看~ 函數 函數大家都知道,我想說的是,js中,在函數內部有兩個特殊...
摘要:前言這段時間一直在消化作用域鏈和閉包的相關知識。而作用域鏈則是這套規則這套規則的具體運行。是變量對象的縮寫那這樣放有什么好處呢我們知道作用域鏈保證了當前執行環境對符合訪問權限的變量和函數的有序訪問。 前言:這段時間一直在消化作用域鏈和閉包的相關知識。之前看《JS高程》和一些技術博客,對于這些概念的論述多多少少不太清楚或者不太完整,包括一些大神的技術文章。這也給我的學習上造成了一些困惑,...
摘要:每一個執行上下文可以訪問的對象包括自身的作用域和父執行上下文的作用域和父父執行上下文作用域直到全局作用域,這就產生了作用域鏈。語句結束后,作用域鏈恢復正常。 0、自己理解 代碼執行或函數調用生成執行上下文(只有當前執行上下文有執行權),該執行上下文內只能訪問當前執行上下文的變量、函數和上一級執行上下文中的變量、函數,激活下一個執行上下文的時候執行權轉移到新的執行上下文,形成執行上下文棧...
摘要:同時構造函數內部的被指定為。這時的作用域鏈是由的活動對象和全局對象組成的。在被調用時,它自身的活動對象被創建,然后添加到了中存儲著的作用域鏈的最前方。當函數執行完畢時活動對象會被從該作用域鏈上刪除。參考自運算符作用域原理譯函數的作用域鏈 new的運行機制 當代碼new Animal(cat)執行時: var obj=Object.create(Animal.prototype); 傳...
閱讀 3806·2023-04-26 02:07
閱讀 3680·2021-10-27 14:14
閱讀 2868·2021-10-14 09:49
閱讀 1634·2019-08-30 15:43
閱讀 2626·2019-08-29 18:33
閱讀 2378·2019-08-29 17:01
閱讀 922·2019-08-29 15:11
閱讀 597·2019-08-29 11:06