您好,登錄后才能下訂單哦!
這篇文章主要介紹“javascript執行上下文的過程是什么”,在日常操作中,相信很多人在javascript執行上下文的過程是什么問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”javascript執行上下文的過程是什么”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!
執行上下文可以說是js代碼執行的一個環境,存放了代碼執行所需的變量,變量查找的作用域鏈規則以及this指向等。同時,它也是js很底層的東西,很多的問題如變量提升、作用域鏈和閉包等都可以在執行上下文中找到答案,所以這也是我們學習執行上下文的原因
執行上下文分為三種:
全局執行上下文:當進入全局代碼時會進行編譯,在編譯中創建全局執行上下文,并生成可執行代碼
函數執行上下文:執行代碼的過程中,如果遇到函數調用,會編譯函數內的代碼和創建函數執行上下文,并創建可執行代碼
eval執行上下文:當使用eval函數的時候,eval的代碼也會被編譯,并創建執行上下文
因為執行上下文是在編譯階段創建的,所以接下來先看一下js代碼的執行過程吧
一段js代碼的執行過程中,先是會進行編譯階段,js引擎會將代碼進行編譯,再進入執行階段
也就是說,js代碼是按照“段”來執行的,具體就是全局代碼就是一段代碼,函數執行也算一段代碼,編譯也是按照“段”來編譯的,也就是一整個js代碼會出現多個編譯階段
編譯階段是一個很復雜的過程,這里只是簡單的介紹:
1、編譯階段完成兩件事情:創建執行上下文和生成可執行代碼
2、執行上下文就包括變量環境和詞法環境和this指向等,創建執行上下文的過程:
如果是普通變量的話,js引擎會將該變量添加到變量環境中并初始化為undefined
如果是函數聲明的話,js引擎會將函數定義添加到變量環境中,然后將函數名執行該函數的位置(內存)
3、接著,js引擎就會把其他的代碼編譯為字節碼,生成可執行代碼
編譯階段完成后,js引擎開始執行可執行代碼,按照順序一行一行執行,當遇到函數或者變量時,會在變量環境中尋找,找不到的話就會報錯
如果遇到賦值語句時,就會將值賦值給變量
變量提升是指在js代碼執行過程中,js引擎把變量的聲明部分和函數聲明部分提升到代碼開頭的“行為”。變量被提升后,會給變量設置默認值undefined
變量提升的實現并不是物理地移動代碼的位置,而是在編譯階段被js引擎放入內存中。
1、普通變量提升會賦值為undefined,函數變量名會將整個函數提升
console.log(fn); // [Function: fn] console.log(a); // undefined function fn() { console.log(111); } var a = 1
2、函數表達式只會將變量提升,不會將函數題提升
3、當有多個相同類型的聲明(同樣是函數聲明或者同樣是普通變量聲明),最后一個聲明會覆蓋之前的聲明
4、當函數聲明與變量聲明同時出現時,函數聲明優先級更高
當然,變量提升有很多的缺陷,所以從es6開始引入了let和const關鍵字,通過let和const聲明的變量不具有變量提升特性,同時也支持塊級作用域,先看一下作用域吧
作用域其實就是一套定義函數調用和變量使用的規則,其中,就有三種作用域:
全局作用域:其中對象在代碼的任何地方都能訪問,其生命周期伴隨著頁面的生命周期
函數作用域:在函數內部定義的變量和函數,只能在內部訪問,外部不能訪問到,函數執行結束后,函數內部定義的變量會被銷毀(函數不會嗎???)
塊級作用域:由代碼塊包含的代碼會形成一個塊級作用域(es6之前沒有),跟函數作用域類似
通過var聲明的變量沒有塊級作用域,通過const let聲明的變量有塊級作用域
那js是如何var的變量提升和支持塊級作用域的呢?這就得從執行上下文的角度說起
編譯階段生成執行上下文:
假設js需要執行一個函數
首先,編譯創建該函數的執行上下文,創建可執行代碼
在編譯階段,所有通過var聲明的變量(包括代碼塊里面的變量)都會被創建并存放在變量環境中,并初始化為undefined
通過let或者const聲明的變量(不包括代碼塊碼里面的變量)都會被創建并存放在詞法環境中,設置為未初始化
至此,編譯階段結束了,開始執行代碼
執行代碼過程中遇到代碼塊時,會先將里面通過let或者const聲明的變量存放在詞法環境中并設置為初始化,其實,在詞法環境內部,維護了一個小型的棧結構,棧底是函數最外層的變量,每遇到一個代碼塊,就將所包含的變量壓入詞法環境的棧結構,代碼塊執行結束后,就將包含的變量彈出
接下來看一段代碼:
function foo(){ var a = 1 let b = 2 { let b = 3 var c = 4 let d = 5 console.log(a) console.log(b) } console.log(b) console.log(c) console.log(d) } foo()
當執行到代碼塊時,對應的執行上下文如下:
foo函數執行上 | |
---|---|
變量環境 a = 1, c = 3 | 詞法環境 {b = 3, d = 5} {b = 2} |
當代碼塊的代碼執行完畢后,對應的詞法環境里的變量就會被彈出棧
foo函數執行上下文 | |
---|---|
變量環境 a = 1, c = 3 | 詞法環境 {b = 2} |
原因
通過上面的分析,我們可以總結變量提升和塊級作用域的實現:
通過編譯階段,通過var聲明的變量已經存在變量環境中并賦值為undefined,所以在執行代碼的任何位置都能狗訪問得到,而不需要在聲明之后才能訪問
而通過let聲明的變量,會被存放在詞法環境中但并未初始化(不包含代碼塊的let或const聲明的變量),所以并不能訪問,而是等到遇到let聲明語句的時候才初始化并賦值
在遇到代碼塊時,會先將let和const聲明的變量存放在詞法環境中并設置為初始化,如果此時在代碼塊中在let聲明變量之前使用該變量,并不會去外部作用域找該變量,因為此時詞法作用域已經存在改變量了,但未初始化,所以此時會報錯誤,這也是let暫時性死區的原因
沿著詞法環境的棧頂向下查詢,如果在詞法環境的某個快中查找到了,就直接返回給js引擎,如果沒有找到,就繼續在變量環境中查找
調用棧是用來管理函數調用關系的一種棧結構
在函數調用之前,會創建對應的執行上下文,并生成對應的可執行代碼
js維護了一個棧結構,每當遇到一個函數調用的時候,就創建一個執行上下文,并壓入該棧中,
這個棧叫做執行上下文棧,也叫做調用棧
當函數執行完畢之后,會將對應的執行上下文彈出棧結構
棧的容量是有限的,當棧容量不夠的時候就有可能發生棧溢出
在函數中如果在當前作用域中找不到所需要的變量,就得沿著作用域鏈往下去查找,直到找到為止
我們都知道,當一段代碼在執行的時候,會有對應的執行上下文,那變量沿著作用域鏈查看的規則也是在執行上下文中設置的
在每個執行上下文中,在變量環境中,都有一個外部引用,用來執行外部的執行上下文,我們把這個外部引用稱為outer
上文已經說到,變量的查找首先會從執行上下文的詞法環境中查找,找不到就在變量環境中查找,再找不到的話就會沿著outer去外部的執行上下文中查找
outer具體引用哪一個執行上下文(作用域),是由詞法作用域決定的
詞法作用域指的是作用域有代碼中函數的聲明位置決定的,也叫做靜態作用域
也就是說,當創建一個執行上下文的時候,其內部的outer就會根據詞法作用域去執行對應的外部執行上下文
在外部的執行上下文中查找時,也是先從詞法環境中開始
function fn() { console.log(a); } function fn1() { let a = 1 fn() } let a = 3 // let聲明的變量是在詞法環境中的 fn1() // 3
在js中,根據詞法作用域的規則,內部函數總是可以訪問外部函數中聲明的變量,當通過調用一個外部函數返回一個內部函數后,即使該外部函數已經執行結束了,但是內部函數引用外部函數的變量依然保存在內存中,我們就把這些變量的集合稱為閉包
function foo() { var myName = " 極客時間 " let test1 = 1 const test2 = 2 var innerBar = { getName:function(){ console.log(test1) return myName }, setName:function(newName){ myName = newName } } return innerBar } var bar = foo() bar.setName(" 極客邦 ") bar.getName() console.log(bar.getName())
上面代碼中,由于存在閉包現象,foo函數執行結束后,內部的變量還會被保存,調用棧如下圖:
當執行到 bar.setName 方法中的myName = "極客邦"這句代碼時,JavaScript 引擎會沿著“當前執行上下文–>foo 函數閉包–> 全局執行上下文”的順序來查找 myName 變量,你可以參考下面的調用棧狀態圖:
如果閉包使用不正確,會很容易造成內存泄漏的,關注閉包是如何回收的能讓你正確地使用閉包。
通常,如果引用閉包的函數是一個全局變量,那么閉包會一直存在直到頁面關閉;但如果這個閉包以后不再使用的話,就會造成內存泄漏。
如果引用閉包的函數是個局部變量,等函數銷毀后,在下次 JavaScript 引擎執行垃圾回收時,判斷閉包這塊內容如果已經不再被使用了,那么 JavaScript 引擎的垃圾回收器就會回收這塊內存。
所以在使用閉包的時候,你要盡量注意一個原則:如果該閉包會一直使用,那么它可以作為全局變量而存在;但如果使用頻率不高,而且占用內存又比較大的話,那就盡量讓它成為一個局部變量。
var bar = { myName:"time.geekbang.com", printName: function () { console.log(myName) } } function foo() { let myName = " 極客時間 " return bar.printName } let myName = " 極客邦 " let _printName = foo() _printName() bar.printName()
執行上下文分為三種,對應的this也只有三種:全局上下文的this,函數中的this,eval中的this
箭頭函數沒有自己的執行上下文
全局上下文的this指向全局對象
函數上下文的this根據四種綁定規則判斷this指向
執行上下文包含this指向
到此,關于“javascript執行上下文的過程是什么”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。