Javascript淺析執(zhí)行機(jī)制的詳情
變量提升
要理解變量提升,我們先從一小段代碼入手:
showName() console.log(myname) var myname = '測(cè)試' function showName() { console.log('函數(shù)showName被執(zhí)行'); }
上面的代碼執(zhí)行之后會(huì)是什么結(jié)果呢?
按照正常的邏輯分析,當(dāng)執(zhí)行到第 1 行的時(shí)候,由于函數(shù) showName 還沒有定義,所以執(zhí)行應(yīng)該會(huì)報(bào)錯(cuò);同樣執(zhí)行第 2 行的時(shí)候,由于變量 myname 也未定義,所以同樣也會(huì)報(bào)錯(cuò)。但是,我們運(yùn)行這段代碼的時(shí)候,并沒有報(bào)錯(cuò),而且打印了結(jié)果。
竟然和預(yù)想的不一樣。難道是在Javascript中,變量和方法沒有定義也可以使用嗎?我們注釋掉myname的定義,在運(yùn)行代碼看結(jié)果會(huì)怎樣?
看來在Javascript中,變量或者函數(shù)的使用有需要定義。
我們?cè)诳聪旅娴拇a執(zhí)行結(jié)果:
showName() console.log('1: ',myname) var myname = '測(cè)試' console.log('2F: ',myname) function showName() { console.log('函數(shù)showName被執(zhí)行'); }
所以,在Javascript中:
1、在執(zhí)行過程中,若使用了未聲明的變量,那么 JavaScript 執(zhí)行會(huì)報(bào)錯(cuò)。
2、在一個(gè)變量定義之前使用它,不會(huì)出錯(cuò),但是該變量的值會(huì)為 undefined,而不是定義時(shí)的值。
3、在一個(gè)變量定義之后使用它,該變量的值就是我們定義的值
4、在一個(gè)函數(shù)定義之前使用它,不會(huì)出錯(cuò),且函數(shù)能正確執(zhí)行。
為什么變量和函數(shù)在定義之前使用不會(huì)報(bào)錯(cuò)呢?這就設(shè)計(jì)到了Javascript中的變量提升。
JavaScript 中的聲明和賦值:
varmyname = '測(cè)試'可以拆分為變量的聲明和賦值。
聲明:
var myname
賦值:
myname = '測(cè)試'
函數(shù)的聲明和賦值:
聲明:
function(){
console.log('函數(shù)showName被執(zhí)行');
}
賦值:
var showName = function(){
console.log('函數(shù)showName被執(zhí)行');
}
ps:
函數(shù)是先聲明變量 showName ,再把function(){console.log('函數(shù)showName被執(zhí)行');}賦值給showName。
變量提升,是指在 JavaScript 代碼執(zhí)行過程中,JavaScript 引擎把變量的聲明部分和函數(shù)的聲明部分提升到代碼開頭的“行為”。變量被提升后,會(huì)給變量設(shè)置默認(rèn)值,這個(gè)默認(rèn)值就是undefined。所以這也解釋了為什么第一段代碼中myname打印的值是undefined了。
所以,第一段代碼可以這樣理解:
JavaScript 代碼的執(zhí)行流程
從概念的字面意義上來看,“變量提升”意味著變量和函數(shù)的聲明會(huì)在物理層面移動(dòng)到代碼的最前面,正如我們所模擬的那樣。但,這并不準(zhǔn)確。實(shí)際上變量和函數(shù)聲明在代碼里的位置是不會(huì)改變的,而且是在編譯階段被 JavaScript 引擎放入內(nèi)存中。
代碼的執(zhí)行分為編譯階段和執(zhí)行階段
編譯階段
根據(jù)上面的理解可知,代碼可以分為變量提升的代碼和可執(zhí)行的代碼。
變量提升部分的代碼:
var myname = undefined function showName() { console.log('函數(shù)showName被執(zhí)行'); }
執(zhí)行部分的代碼:
showName() console.log(myname) myname = '極客時(shí)間'
代碼經(jīng)過編譯之后會(huì)生產(chǎn)兩部分內(nèi)容:執(zhí)行上下文和可執(zhí)行代碼
執(zhí)行上下文是 JavaScript 執(zhí)行一段代碼時(shí)的運(yùn)行環(huán)境,比如調(diào)用一個(gè)函數(shù),就會(huì)進(jìn)入這個(gè)函數(shù)的執(zhí)行上下文,確定該函數(shù)在執(zhí)行期間用到的諸如 this、變量、對(duì)象以及函數(shù)等。
在執(zhí)行上下文中存在一個(gè)變量環(huán)境的對(duì)象(Viriable Environment),該對(duì)象中保存了變量提升的內(nèi)容,比如上面代碼中的變量 myname 和函數(shù) showName,都保存在該對(duì)象中。
接下來,我們根據(jù)代碼一行一行的分析下是如何生成變量環(huán)境對(duì)象的:
第 1 行和第 2 行,由于這兩行代碼不是聲明操作,所以 JavaScript 引擎不會(huì)做任何處理;
第 3 行,由于這行是經(jīng)過 var 聲明的,因此 JavaScript 引擎將在環(huán)境對(duì)象中創(chuàng)建一個(gè)名為 myname 的屬性,并使用 undefined 對(duì)其初始化;
第 4 行,JavaScript 引擎發(fā)現(xiàn)了一個(gè)通過 function 定義的函數(shù),所以它將函數(shù)定義存儲(chǔ)到堆 (HEAP)中,并在環(huán)境對(duì)象中創(chuàng)建一個(gè) showName 的屬性,然后將該屬性值指向堆中函數(shù)的位置(不了解堆也沒關(guān)系,JavaScript 的執(zhí)行堆和執(zhí)行棧我會(huì)在后續(xù)文章中介紹)。這樣就生成了變量環(huán)境對(duì)象。接下來 JavaScript 引擎會(huì)把聲明以外的代碼編譯為字節(jié)碼。
執(zhí)行階段
我們就來一行一行分析下這個(gè)執(zhí)行過程:
(1)當(dāng)執(zhí)行到 showName 函數(shù)時(shí),JavaScript 引擎便開始在變量環(huán)境對(duì)象中查找該函數(shù),由于變量環(huán)境對(duì)象中存在該函數(shù)的引用,所以 JavaScript 引擎便開始執(zhí)行該函數(shù),并輸出“函數(shù) showName 被執(zhí)行”結(jié)果。
(2)接下來打印“myname”信息,JavaScript 引擎繼續(xù)在變量環(huán)境對(duì)象中查找該對(duì)象,由于變量環(huán)境存在 myname 變量,并且其值為 undefined,所以這時(shí)候就輸出 undefined。
(3)接下來執(zhí)行第 3 行,把“極客時(shí)間”賦給 myname 變量,賦值后變量環(huán)境中的 myname 屬性值改變?yōu)?ldquo;極客時(shí)間”。
代碼中出現(xiàn)相同的變量或者函數(shù)怎么辦
function showName() { console.log('極客邦'); } showName(); function showName() { console.log('極客時(shí)間'); } showName();
上面的代碼會(huì)輸出什么呢?
我們來分析下其完整執(zhí)行流程:
(1)首先是編譯階段。遇到了第一個(gè) showName 函數(shù),會(huì)將該函數(shù)體存放到變量環(huán)境中。接下來是第二個(gè) showName 函數(shù),繼續(xù)存放至變量環(huán)境中,但是變量環(huán)境中已經(jīng)存在一個(gè) showName 函數(shù)了,此時(shí),第二個(gè) showName 函數(shù)會(huì)將第一個(gè) showName 函數(shù)覆蓋掉。這樣變量環(huán)境中就只存在第二個(gè) showName 函數(shù)了。
(2)接下來是執(zhí)行階段。先執(zhí)行第一個(gè) showName 函數(shù),但由于是從變量環(huán)境中查找 showName 函數(shù),而變量環(huán)境中只保存了第二個(gè) showName 函數(shù),所以最終調(diào)用的是第二個(gè)函數(shù),打印的內(nèi)容是“極客時(shí)間”。第二次執(zhí)行 showName 函數(shù)也是走同樣的流程,所以輸出的結(jié)果也是“極客時(shí)間”。綜上所述,一段代碼如果定義了兩個(gè)相同名字的函數(shù),那么最終生效的是最后一個(gè)函數(shù)。
console.log('1:', myname) var myname = 'zhangsan' console.log('2:', myname) var myname = 'lisi' console.log('3:', myname)
上面的代碼的執(zhí)行結(jié)果又是怎樣的呢?
分析:
(1)編譯階段。遇到第一個(gè)var myname時(shí),會(huì)將該變量存放到變量環(huán)境中,值為undefined。接著遇到第二個(gè)var myname時(shí),繼續(xù)存放至變量環(huán)境中,值也為undefined,但是變量環(huán)境中已經(jīng)存在一個(gè) myname變量了,此時(shí),第二個(gè) myname會(huì)將第一個(gè) myname覆蓋掉。這樣變量環(huán)境中就只存在第二個(gè) myname變量了,值為undefined。
(2)執(zhí)行階段。執(zhí)行第一個(gè)console打印時(shí),myname的值為undefined,所以打印的是undefined。直接是對(duì)myname的賦值,值為zhangsan,所以執(zhí)行第二個(gè)console的時(shí)候,打印的是zhangsan,接著執(zhí)行第二條賦值語句,myname被賦值為lisi,所以在執(zhí)行第三個(gè)console時(shí),打印的是lisi。
什么情況下會(huì)進(jìn)行編譯并創(chuàng)建執(zhí)行上下文?
1、當(dāng)Javascript執(zhí)行全家代碼時(shí),會(huì)編譯全局代碼并創(chuàng)建全局執(zhí)行上下文,而且整個(gè)頁面的生命周期內(nèi),全局執(zhí)行上下文只有一份
2、當(dāng)調(diào)用一個(gè)函數(shù)的時(shí)候,函數(shù)體內(nèi)的代碼會(huì)被編譯,并創(chuàng)建函數(shù)執(zhí)行上下文,一般情況下,函數(shù)執(zhí)行結(jié)束之后,創(chuàng)建的函數(shù)執(zhí)行上下文會(huì)被銷毀。
3、當(dāng)使用eval函數(shù)的時(shí)候,eval的代碼會(huì)被編譯,并創(chuàng)建執(zhí)行上下文。
到此這篇關(guān)于Javascript淺析執(zhí)行機(jī)制的詳情的文章就介紹到這了,更多相關(guān)Js執(zhí)行機(jī)制內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JavaScript異步回調(diào)的Promise模式封裝實(shí)例
這篇文章主要介紹了JavaScript異步回調(diào)的Promise模式封裝實(shí)例,本文通過分析easyjs的源碼得出,實(shí)例均參考easyjs,需要的朋友可以參考下2014-06-06使用Taro實(shí)現(xiàn)小程序商城的購物車功能模塊的實(shí)例代碼
這篇文章主要介紹了使用Taro實(shí)現(xiàn)的小程序商城的購物車功能模塊,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-06-06javascript實(shí)現(xiàn)簡單飛機(jī)大戰(zhàn)小游戲
這篇文章主要為大家詳細(xì)介紹了javascript實(shí)現(xiàn)簡單飛機(jī)大戰(zhàn)小游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-05-05webpack打包中path.resolve(__dirname, 'dist')的含義解
這篇文章主要介紹了webpack打包中path.resolve(__dirname, 'dist')的含義解析,path:path.resolve(__dirname, 'dist')就是在打包之后的文件夾上拼接了一個(gè)文件夾,在打包時(shí),直接生成,本文給大家講解的非常詳細(xì),需要的朋友可以參考下2023-05-05D3.js實(shí)現(xiàn)力向?qū)D的繪制教程詳解
力向?qū)D是繪圖的一種算法,實(shí)現(xiàn)了用以模擬粒子物理運(yùn)動(dòng)的?velocity?Verlet?數(shù)值積分器。本文將利用D3.js實(shí)現(xiàn)力向?qū)D的繪制,需要的可以參考一下2022-11-11JS獲取html元素的標(biāo)記名實(shí)現(xiàn)方法
下面小編就為大家?guī)硪黄狫S獲取html元素的標(biāo)記名實(shí)現(xiàn)方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-10-10Javascript 鼠標(biāo)移動(dòng)上去 滑塊跟隨效果代碼分享
這篇文章主要介紹了Javascript 鼠標(biāo)移動(dòng)上去 滑塊跟隨效果代碼,有需要的朋友可以參考一下2013-11-11