摘要:中沒有可執(zhí)行的函數(shù)了,執(zhí)行完出棧。當某個函數(shù)被調(diào)用時,會創(chuàng)建一個執(zhí)行環(huán)境及相應的作用域鏈。檢查當前環(huán)境中的函數(shù)聲明使用聲明的。確定指向所以說的指向,是在函數(shù)執(zhí)行時確定的。
理解js 的執(zhí)行過程是很重要的,比如,作用域,作用域鏈,變量提升,閉包啊,要想明白這些,你就得搞懂函數(shù)執(zhí)行時到底發(fā)生了什么! 一、執(zhí)行環(huán)境(Execution Context)又稱執(zhí)行上下文
當代碼執(zhí)行時都會產(chǎn)生一個執(zhí)行環(huán)境。JavaScript中的執(zhí)行環(huán)境可以分為三種。
全局環(huán)境:在瀏覽器中,全局環(huán)境被認為是window對象,因此,所有的全局變量和函數(shù)都作為window對象的 屬性 和 方法 創(chuàng)建的。
函數(shù)環(huán)境:當一個函數(shù)執(zhí)行時,就會創(chuàng)建該函數(shù)的執(zhí)行環(huán)境,在其中執(zhí)行代碼。
eval(不建議使用,可忽略)
函數(shù)內(nèi),沒有使用var 聲明的變量,在非嚴格模式下為window的屬性,即全局變量。
js 是根據(jù)函數(shù)的調(diào)用(執(zhí)行) 來決定 執(zhí)行順序的。每當一個函數(shù)被調(diào)用時,js 會為其創(chuàng)建執(zhí)行環(huán)境,js引擎就會把這個執(zhí)行環(huán)境 放入一個棧中 來處理。
這個棧,我們稱之為函數(shù)調(diào)用棧(call stack)。棧底永遠都是全局環(huán)境,而棧頂就是當前正在執(zhí)行函數(shù)的環(huán)境。當棧頂?shù)膱?zhí)行環(huán)境 執(zhí)行完之后,就會出棧,并把執(zhí)行權交給之前的執(zhí)行環(huán)境。
看栗子說話:
function A(){ console.log("this is A"); function B(){ console.log("this is B"); } B(); } A();
那么這段代碼執(zhí)行的情況就是這樣了。
首先 A() ;A 函數(shù)執(zhí)行了,A執(zhí)行環(huán)境入棧。
A函數(shù)執(zhí)行時,遇到了 B(),B 又執(zhí)行了,B入棧。
B中沒有可執(zhí)行的函數(shù)了,B執(zhí)行完 出棧。
繼續(xù)執(zhí)行A, A中沒有可執(zhí)行的函數(shù)了,A執(zhí)行完 出棧。
再來個不常規(guī)的:
function A(){ function B(){ console.log(say); } return B; } var C = A(); C();
首先 A() ;A 函數(shù)執(zhí)行了,A執(zhí)行環(huán)境入棧。
繼續(xù)執(zhí)行A, A中沒有可執(zhí)行的函數(shù)了,A執(zhí)行完 出棧。
然后C(), 這時的C 就是 B,A 執(zhí)行后,把B返回 賦值給了C,B執(zhí)行環(huán)境入棧。
B中 沒有可執(zhí)行的函數(shù)了,B執(zhí)行完 出棧。
眼尖的同學,估計看出來了,它怎么像閉包呢?其實,稍微改動下,它就是閉包了。
function A(){ var say = 666 function B(){ console.log(say); } return B; } var C = A(); C(); //666
這就是閉包了,但是這次我們不講閉包,你就知道,它是的執(zhí)行是怎么回事就行。
現(xiàn)在我們已經(jīng)知道,每當一個函數(shù)執(zhí)行時,一個新的執(zhí)行環(huán)境就會被創(chuàng)建出來。其實,在js引擎內(nèi)部,這個環(huán)境的創(chuàng)建過程可分為兩個階段:
A. 建立階段(發(fā)生在調(diào)用(執(zhí)行)一個函數(shù)時,但是在執(zhí)行函數(shù)內(nèi)部的具體代碼之前)
1.建立活動對象; 2.構建作用域鏈; 3.確定this的值。
B. 代碼執(zhí)行階段(執(zhí)行函數(shù)內(nèi)部的具體代碼)
? ? ? ?1.變量賦值;
? ? ? ?2.執(zhí)行其它代碼。
需要注意的是,作用域鏈是創(chuàng)建函數(shù)的時候就創(chuàng)建了,此時的鏈只有全局變量對象,保存在函數(shù)的[[Scope]]屬性中,然后函數(shù)執(zhí)行時的,只是通過復制該屬性中的對象 來 構建作用域鏈。本文后面還有說明。
看圖更清晰!
如果把函數(shù)執(zhí)行環(huán)境看成一個對象的話:
executionContextObj = { //執(zhí)行上下文對象 AtiveObject: { }, //活動對象 scopeChain: { }, //作用域鏈 this: {} //this }
//下面這段內(nèi)容,感興趣的可以看下,不感興趣,就跳過哈。
也許你在別家看到跟我的不一樣,人家寫的是建立變量對象。下面我來說說我得想法吧!
之前我按照 首先建立變量對象,其后,變量對象轉變?yōu)榛顒訉ο蟮囊?guī)則 去理解,但是呢,通過我分析JavaScript高級程序設計第三版,4.2節(jié) 和 7.2節(jié),發(fā)現(xiàn)根本就不符合邏輯。
然后,我根據(jù)分析,得出了我的結論:變量對象 是執(zhí)行環(huán)境中保存著環(huán)境中定義的所有變量和函數(shù) 的對象 的統(tǒng)稱。而活動對象,是函數(shù)執(zhí)行環(huán)境中創(chuàng)建的,它不僅保存著函數(shù)執(zhí)行環(huán)境中定義的變量和函數(shù),并且獨有一個arguments 屬性。因此,活動對象也可稱之為變量對象。
這樣,很多東西就說的通了。
比如(以下都是來自JavaScript高級程序設計第三版,4.2節(jié) 和 7.2節(jié) 中原文):
如果這個環(huán)境是函數(shù),則將其活動對象(activation object)作為變量對象。活動對象在最開始時只包含一個變量,即 arguments 對象(這個對象在全局環(huán)境中是不存在的)。
當某個函數(shù)被調(diào)用時,會創(chuàng)建一個執(zhí)行環(huán)境(execution context)及相應的作用域鏈。
然后,使用 arguments 和其他命名參數(shù)的值來初始化函數(shù)的活動對象。
每個執(zhí)行環(huán)境都有一個表示變量的對象——變量對象。
此后,又有一個活動對象(在此作為變量對象使用)被創(chuàng)建并被推入執(zhí)
行環(huán)境作用域鏈的前端。對于這個例子中 compare() 函數(shù)的執(zhí)行環(huán)境而言,其作用域鏈中包含兩個變量對象:本地活動對象和全局變量對象。
有興趣的可以去看看這本書上說的,有不同的想法可以積極留言,咱們好好探討,哈哈。
(一)建立階段1、建立活動對象(AO)
A. 建立arguments對象,檢查當前上下文中的參數(shù),建立該對象下的屬性以及屬性值 。
B. 檢查當前環(huán)境中的函數(shù)聲明(使用function 聲明的)。每找到一個函數(shù)聲明,就在活動對象下面用函數(shù)名建立一個屬性,屬性值就是指向該函數(shù)在內(nèi)存中的地址的一個引用,如果上述函數(shù)名已經(jīng)存在于活動對象下,那么則會被新的函數(shù)引用所覆蓋。
C. 檢查當前上下文中的變量聲明(使用 var 聲明的)。每找到一個變量聲明,就在活動對象下面用變量名建立一個屬性,該屬性值為undefined。如果該屬性名已存在,則忽略新的聲明。
function test(){ function a(){}; var b; } test();
test 函數(shù) 的活動對象:
testAO: { //test變量對象 arguments: { ... }; a:function(){}; b:undefined }
變量作用域
javaScript 中,只有兩種變量作用域,一種是局部變量作用域,又稱函數(shù)作用域。另一個則是全局作用域。
什么變量提升問題的根本原因就在建立階段了。
console.log(A); function A(){}; console.log(B); var A = 666; var B = 566; console.log(A); console.log(B); //function A //undefined //666 //566
上面的實際順序就是這樣的了
function A(){}; //var A; 這個var 聲明的 同名 A,會被忽略 var B = undefined; console.log(A); console.log(B); A = 666; //給A 重新賦值 B = 566; //給B 賦值 console.log(A); console.log(B);
注意第三點,使用var 聲明時,如果VO對象下,該屬性已存在,忽略新的var 聲明。
因為A 使用 function 聲明,VO對象下,創(chuàng)建A屬性,然后 var 聲明時,檢索發(fā)現(xiàn)已經(jīng)有該屬性了,就會忽略 var A 的聲明,不會把A 設置為 undefined。
2、構建作用域鏈
作用域鏈的最前端,始終都是當前執(zhí)行的代碼所在函數(shù)的活動對象。下一個AO(活動對象)為包含本函數(shù)的外部函數(shù)的AO,以此類推。最末端,為全局環(huán)境的變量對象。
注意:雖然作用域鏈是在函數(shù)調(diào)用時構建的,但是,它跟調(diào)用順序(進入調(diào)用棧的順序)無關,因為它只跟 包含關系(函數(shù) 包含 函數(shù) 的嵌套關系) 有關。
可能比較繞口,還是來個小栗子,再來個圖
function fa(){ var va = "this is fa"; function fb(){ var vb = "this is fb"; console.log(vb); console.log(va); } return fb; } var fc = fa(); fc(); //"this is fb" //"this is fa"
函數(shù)調(diào)用棧的情況就是這樣:
那么把函數(shù) fb 的執(zhí)行環(huán)境比作對象(建立階段):
fbEC = { //執(zhí)行上下文對象 fbAO: { //活動對象 AO arguments: { ... }; //arguments 對象 vb: undefined //變量聲明建立的屬性,設置為undefined }, scopeChain: [ AO(fa), AO(fb), VO(window) ], //作用域鏈 this: { ... } //this }
fb作用域的展開就是這樣的:
fb 函數(shù) 被 fa 函數(shù) 包含, fa 函數(shù) 被 window 全局環(huán)境包含。作用域鏈只跟包含關系有關!
注意:作用域鏈是單向的,因此,函數(shù)內(nèi)的可以訪問函數(shù)外 和 全局的變量,函數(shù),但是反過來,函數(shù)外,全局內(nèi) 不能訪問函數(shù)內(nèi)的變量,函數(shù)。
3、確定 this 指向
所以說 this 的指向,是在函數(shù)執(zhí)行時確定的。
1、變量賦值
根據(jù)代碼順序執(zhí)行,遇到變量賦值時, 給對應的變量賦值。
function getColor(){ console.log(color); var color; console.log(color); color = "red"; console.log(color); } getColor(); //undefined //undefined //"red";
3、執(zhí)行其他代碼。
當函數(shù)執(zhí)行完畢后,局部活動對象就會被銷毀(也就是說,局部的變量,函數(shù),arguments 等都會被銷毀),內(nèi)存中僅保存全局作用域(全局執(zhí)行環(huán)境的變量對象)。
這句話對理解閉包很重要,隨后,我會出一個閉包的文章,敬請期待!
文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://www.ezyhdfw.cn/yun/96050.html
摘要:如下代碼輸出的結果是代碼執(zhí)行分為兩個大步預解析的過程代碼的執(zhí)行過程預解析與變量聲明提升程序在執(zhí)行過程中,會先將代碼讀取到內(nèi)存中檢查,會將所有的聲明在此進行標記,所謂的標記就是讓解析器知道有這個名字,后面在使用名字的時候不會出現(xiàn)未定義的錯誤。 showImg(https://segmentfault.com/img/remote/1460000012922850); 如下代碼輸出的結果是...
摘要:為了更好的理解,在閱讀此文之前建議先閱讀上一篇進擊之詞法作用域與作用域鏈什么是閉包閉包的含義就是閉合,包起來,簡單的來說,就是一個具有封閉功能與包裹功能的結構。在中函數(shù)構成閉包。 為了更好的理解,在閱讀此文之前建議先閱讀上一篇《進擊JavaScript之詞法作用域與作用域鏈》 1.什么是閉包 閉包的含義就是閉合,包起來,簡單的來說,就是一個具有封閉功能與包裹功能的結構。所謂的閉包就是...
摘要:一作用域域表示的就是范圍,即作用域,就是一個名字在什么地方可以使用,什么時候不能使用。概括的說作用域就是一套設計良好的規(guī)則來存儲變量,并且之后可以方便地找到這些變量。 一、作用域 域表示的就是范圍,即作用域,就是一個名字在什么地方可以使用,什么時候不能使用。想了解更多關于作用域的問題推薦閱讀《你不知道的JavaScript上卷》第一章(或第一部分),從編譯原理的角度說明什么是作用域。概...
摘要:此時產(chǎn)生了閉包。導致,函數(shù)的活動對象沒有被銷毀。是不是跟你想的不一樣其實,這個例子重點就在函數(shù)上,這個函數(shù)的第一個參數(shù)接受一個函數(shù)作為回調(diào)函數(shù),這個回調(diào)函數(shù)并不會立即執(zhí)行,它會在當前代碼執(zhí)行完,并在給定的時間后執(zhí)行。 上一節(jié)說了執(zhí)行上下文,這節(jié)咱們就乘勝追擊來搞搞閉包!頭疼的東西讓你不再頭疼! 一、函數(shù)也是引用類型的。 function f(){ console.log(not cha...
摘要:匿名函數(shù)是不能單獨寫的,所以就提不上立即執(zhí)行了。六立即執(zhí)行函數(shù)在閉包中的應用立即執(zhí)行函數(shù)能配合閉包保存狀態(tài)。來看下上節(jié)內(nèi)容中閉包的例子現(xiàn)在,我們來利用立即執(zhí)行函數(shù)來簡化它第一個匿名函數(shù)執(zhí)行完畢后,返回了第二個匿名函數(shù)。 前面的閉包中,提到與閉包相似的立即執(zhí)行函數(shù),感覺兩者還是比較容易弄混吧,嚴格來說(因為犀牛書和高程對閉包的定義不同),立即執(zhí)行函數(shù)并不屬于閉包,它不滿足閉包的三個條件。...
閱讀 1893·2023-04-25 14:33
閱讀 3475·2021-11-22 15:22
閱讀 2562·2021-09-30 09:48
閱讀 2875·2021-09-14 18:01
閱讀 1822·2019-08-30 15:55
閱讀 3126·2019-08-30 15:53
閱讀 2232·2019-08-30 15:44
閱讀 732·2019-08-30 10:58