欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

JavaScript在瀏覽器中的執(zhí)行機(jī)制

 更新時(shí)間:2023年09月12日 08:41:42   作者:托兒所夜十三  
既然說(shuō)到了JavaScript,那么就會(huì)繞不過(guò)去執(zhí)行上下文,只有理解了執(zhí)行上下文才能更好的理解JavaScript本身,不過(guò)本部分不是專門(mén)講解?JavaScript的,主要從JavaScript的執(zhí)行順序開(kāi)始介紹一下JavaScript是怎樣運(yùn)行的

既然說(shuō)到了 JavaScript,那么就會(huì)繞不過(guò)去執(zhí)行上下文,只有理解了執(zhí)行上下文才能更好的理解 JavaScript 本身,比如變量提升、棧溢出、作用域和閉包等。

不過(guò)本部分不是專門(mén)講解 JavaScript 的,所有不會(huì)對(duì) JavaScript 本身做過(guò)多介紹,主要從 JavaScript 的執(zhí)行順序開(kāi)始介紹一下 JavaScript 是怎樣運(yùn)行的。

首先先看一下下面的代碼:

showName()
console.log(myname)
var myname = '掘金'
function showName() {
  console.log('function showName run')
}

作為前端大家都知道上面的代碼是同步的會(huì)順序執(zhí)行,那么輸出應(yīng)該是:

  • 當(dāng)執(zhí)行第一行時(shí)因?yàn)?nbsp;showName 方法還沒(méi)有定義應(yīng)該會(huì)報(bào)錯(cuò)。
  • 同樣執(zhí)行第二行時(shí)因?yàn)樽兞?nbsp;name 也沒(méi)有定義所有也會(huì)報(bào)錯(cuò)。

但是如下圖執(zhí)行結(jié)果跟我們想的并不一樣:

第1行輸出 “function showName run”,第2行輸出 “undefined”,這和前面想象中的順序執(zhí)行有點(diǎn)不一樣?。?/p>

上面結(jié)果可以看出函數(shù)或者變量可以在定義之前使用,如果沒(méi)有定義代碼還能正常執(zhí)行嗎,可以參考下面代碼去掉第三行 myName 的定義:

showName()
console.log(myname)
function showName() {
  console.log('function showName run')
}

使用了未定義的變量——執(zhí)行報(bào)錯(cuò)

從上面兩段代碼的執(zhí)行結(jié)果來(lái)看,我們可以得出如下三個(gè)結(jié)論。

  • 在執(zhí)行過(guò)程中,若使用了未聲明的變量,那么JavaScript執(zhí)行會(huì)報(bào)錯(cuò)。
  • 在一個(gè)變量定義之前使用它,不會(huì)出錯(cuò),但是該變量的值會(huì)為undefined,而不是定義時(shí)的值。
  • 在一個(gè)函數(shù)定義之前使用它,不會(huì)出錯(cuò),且函數(shù)能正確執(zhí)行。

第一個(gè)結(jié)論很好理解,因?yàn)樽兞繘](méi)有定義,這樣在執(zhí)行JavaScript代碼時(shí),就找不到該變量,所以JavaScript會(huì)拋出錯(cuò)誤。

但是對(duì)于第二個(gè)和第三個(gè)結(jié)論,就挺讓人費(fèi)解的,要解決這兩個(gè)問(wèn)題就要明白接下來(lái)我們要介紹的內(nèi)容-變量提升。

變量提升

在正式了解變量提升之前,我們需要先理解兩個(gè) JavaScript 概念聲明和賦值。

var myName = "掘金"

這段代碼可以這樣拆解看:

var myName        //聲明部分
myName = "掘金"   //賦值部分

上面說(shuō)明了變量的聲明和賦值,接下來(lái)說(shuō)一下函數(shù)的聲明和賦值:

function foo(){
  console.log('foo')
}
var bar = function(){
  console.log('bar')
}

第一個(gè)函數(shù)foo是一個(gè)完整的函數(shù)聲明,也就是說(shuō)沒(méi)有涉及到賦值操作;第二個(gè)函數(shù)是先聲明變量bar,再把function(){console.log('bar')}賦值給bar。為了直觀理解,你可以參考下圖:

通過(guò)上面我們了解了變量和函數(shù)的聲明和賦值了,接下來(lái)說(shuō)一說(shuō)什么是變量提升。

所謂的變量提升就是指 JavaScript 代碼在執(zhí)行過(guò)程中, JavaScript 引擎把變量和函數(shù)的聲明部分提升到代碼開(kāi)頭的”行為“。變量提升后,會(huì)給變量設(shè)置默認(rèn)值,這個(gè)默認(rèn)值就是我們熟悉的 undefined 。

具體變化如下代碼:

/*
* 變量提升部分
*/
// 把變量 myname提升到開(kāi)頭,
// 同時(shí)給myname賦值為undefined
var myname = undefined
// 把函數(shù)showName提升到開(kāi)頭
function showName() {
    console.log('function showName run');
}
/*
* 可執(zhí)行代碼部分
*/
showName()
console.log(myname)
// 去掉var聲明部分,保留賦值語(yǔ)句
myname = '掘金'

可以通過(guò)下圖更直觀的感受:

從圖中可以看出,對(duì)原來(lái)的代碼主要做了兩處調(diào)整:

  • 第一處是把聲明的部分都提升到了代碼開(kāi)頭,如變量 myName 和函數(shù) showName,并給變量設(shè)置默認(rèn)值 undefined;
  • 第二處是移除原本聲明的變量和函數(shù),如 var myName = '掘金'的語(yǔ)句,移除了 var 聲明,整個(gè)移除 showName 的函數(shù)聲明。

通過(guò)這兩步,就可以實(shí)現(xiàn)變量提升的效果。你也可以執(zhí)行這段模擬變量提升的代碼,其輸出結(jié)果和第一段代碼應(yīng)該是完全一樣的。

通過(guò)這段模擬的變量提升代碼,明白了可以在定義之前使用變量或者函數(shù)的原因——函數(shù)和變量在執(zhí)行之前都提升到了代碼開(kāi)頭。

let和const會(huì)變量提升嗎?

我們執(zhí)行下面代碼進(jìn)行嘗試:

console.log(myName)
const myName = '掘金'

上圖的報(bào)錯(cuò)為:ReferenceError: Cannot access 'variable' before initialization。和未定義的的報(bào)錯(cuò)不同,未定義報(bào)錯(cuò)如下

console.log(myName1)
const myName = '掘金'

由此可見(jiàn),用 var 定義的變量可以在聲明之前被訪問(wèn)而不報(bào)錯(cuò),但是用 let 或 const 定義的變量卻不行。

由 let 或 const 定義的變量提升時(shí)不會(huì)默認(rèn)初始化,所以在聲明之前訪問(wèn)會(huì)報(bào)錯(cuò):ReferenceError: Cannot access 'variable' before initialization。

然而由 var 定義的變量提升時(shí)會(huì)被初始化為默認(rèn)值 undefined,所以在聲明之前訪問(wèn)會(huì)得到 undefined。 這里面就是涉及到了一個(gè)概念 暫時(shí)性死區(qū),let/const 定義的變量被提升卻無(wú)法正常訪問(wèn),是因?yàn)榇嬖?strong>暫時(shí)性死區(qū)(Temporal Dead Zone) 。

JavaScript 代碼的執(zhí)行順序

通過(guò)上面介紹變量提升,你可能會(huì)覺(jué)得變量和函數(shù)會(huì)在物理層面中被移動(dòng)到代碼最前面,就是我們圖中展示的那樣,但是,實(shí)際上變量和函數(shù)聲明在代碼里的位置并不會(huì)改變,而是在編譯階段被 JavaScript 引擎放入到內(nèi)存中,你沒(méi)看錯(cuò),一段 JavaScript 代碼在被執(zhí)行前需要 JavaScript 引擎編譯,編譯完成后才會(huì)被執(zhí)行,如下圖所示:

1.編譯階段

編譯和變量提升有什么關(guān)系呢?我們可以從下面兩部分代碼來(lái)看:

第一部分:變量提升部分的代碼。

var?myName?=?undefined
function?showName()?{
????console.log('function showName run');
}

第二部分:執(zhí)行部分的代碼。

showName()
console.log(myName)
myName?=?'掘金'

可以把JavaScript的執(zhí)行流程細(xì)化,如下圖所示:

從上圖可以看出,輸入一段代碼,經(jīng)過(guò)編譯后,會(huì)生成兩部分內(nèi)容:執(zhí)行上下文(Execution context)和可執(zhí)行代碼。

執(zhí)行上下文是JavaScript執(zhí)行一段代碼時(shí)的運(yùn)行環(huán)境,比如調(diào)用一個(gè)函數(shù),就會(huì)進(jìn)入這個(gè)函數(shù)的執(zhí)行上下文,確定該函數(shù)在執(zhí)行期間用到的諸如this、變量、對(duì)象以及函數(shù)等。

可以用下面的偽代碼簡(jiǎn)單的理解環(huán)境變量對(duì)象:

VariableEnvironment:
     myname -> undefined, 
     showName ->function : {console.log(myname)

接下來(lái)看一下生成環(huán)境變量的過(guò)程:

showName()
console.log(myName)
var myName = '掘金'
function showName() {
    console.log('function showName run');
}
  • 第1行和第2行,由于這兩行代碼不是聲明操作,所以JavaScript引擎不會(huì)做任何處理;
  • 第3行,由于這行是經(jīng)過(guò)var聲明的,因此JavaScript引擎將在環(huán)境對(duì)象中創(chuàng)建一個(gè)名為 myName的屬性,并使用 undefined 對(duì)其初始化;
  • 第4行,JavaScript引擎發(fā)現(xiàn)了一個(gè)通過(guò) function 定義的函數(shù),所以它將函數(shù)定義存儲(chǔ)到堆(HEAP)中,并在環(huán)境對(duì)象中創(chuàng)建一個(gè)showName的屬性,然后將該屬性值指向堆中函數(shù)的位置。

這樣就生成了變量環(huán)境對(duì)象。接下來(lái)JavaScript引擎會(huì)把聲明以外的代碼編譯為字節(jié)碼,可以類比如下的偽代碼:

showName()
console.log(myname)
myname = '掘金'

好了,現(xiàn)在有了執(zhí)行上下文和可執(zhí)行代碼了,那么接下來(lái)就到了執(zhí)行階段了。

2. 執(zhí)行階段

JavaScript引擎開(kāi)始執(zhí)行“可執(zhí)行代碼”,按照順序一行一行地執(zhí)行。下面我們就來(lái)一行一行分析下這個(gè)執(zhí)行過(guò)程:

  • 當(dāng)執(zhí)行到 showName 函數(shù)時(shí),JavaScript 引擎便開(kāi)始在變量環(huán)境對(duì)象中查找該函數(shù),由于變量環(huán)境對(duì)象中存在該函數(shù)的引用,所以 JavaScript 引擎便開(kāi)始執(zhí)行該函數(shù),并輸出“函數(shù) showName 被執(zhí)行”結(jié)果。
  • 接下來(lái)打印 myName 信息,JavaScript 引擎繼續(xù)在變量環(huán)境對(duì)象中查找該對(duì)象,由于變量環(huán)境存在 myName 變量,并且其值為 undefined,所以這時(shí)候就輸出 undefined
  • 接下來(lái)執(zhí)行第3行,把“掘金”賦給 myName 變量,賦值后變量環(huán)境中的 myname 屬性值改變?yōu)?ldquo;掘金”,變量環(huán)境如下所示:
VariableEnvironment:
?????myname?->?"掘金",?
?????showName?->function?:?{console.log(myName)

代碼中出現(xiàn)相同的變量或者函數(shù)怎么辦?

現(xiàn)在你已經(jīng)知道了,在執(zhí)行一段JavaScript代碼之前,會(huì)編譯代碼,并將代碼中的函數(shù)和變量保存到執(zhí)行上下文的變量環(huán)境中,那么如果代碼中出現(xiàn)了重名的函數(shù)或者變量,JavaScript引擎會(huì)如何處理?

我們先看下面這樣一段代碼:

function?showName()?{
????console.log('沸點(diǎn)');
}
showName();
function?showName()?{
????console.log('掘金');
}
showName();?

分析這兩次調(diào)用打印出來(lái)的值是什么嗎?

完整執(zhí)行流程:

  • 首先是編譯階段。遇到了第一個(gè) showName 函數(shù),會(huì)將該函數(shù)體存放到變量環(huán)境中。接下來(lái)是第二個(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ù)了。
  • 接下來(lái)是執(zhí)行階段。先執(zhí)行第一個(gè) showName 函數(shù),但由于是從變量環(huán)境中查找 showName 函數(shù),而變量環(huán)境中只保存了第二個(gè) showNam e函數(shù),所以最終調(diào)用的是第二個(gè)函數(shù),打印的內(nèi)容是“掘金”。第二次執(zhí)行showName函數(shù)也是走同樣的流程,所以輸出的結(jié)果也是“掘金”。

綜上所述,一段代碼如果定義了兩個(gè)相同名字的函數(shù),那么最終生效的是最后一個(gè)函數(shù)。

總結(jié)

好了,今天就到這里,下面我來(lái)簡(jiǎn)單總結(jié)下今天的主要內(nèi)容:

  • JavaScript 代碼執(zhí)行過(guò)程中,需要先做變量提升,而之所以需要實(shí)現(xiàn)變量提升,是因?yàn)?JavaScript 代碼在執(zhí)行之前需要先編譯。
  • 在編譯階段,變量和函數(shù)會(huì)被存放到變量環(huán)境中,變量的默認(rèn)值會(huì)被設(shè)置為 undefined;在代碼執(zhí)行階段,JavaScript 引擎會(huì)從變量環(huán)境中去查找自定義的變量和函數(shù)。
  • 如果在編譯階段,存在兩個(gè)相同的函數(shù),那么最終存放在變量環(huán)境中的是最后定義的那個(gè),這是因?yàn)楹蠖x的會(huì)覆蓋掉之前定義的。

以上就是今天所講的主要內(nèi)容,當(dāng)然,學(xué)習(xí)這些內(nèi)容并不是讓你掌握一些 JavaScript 小技巧,其主要目的是讓你清楚JavaScript的執(zhí)行機(jī)制:先編譯,再執(zhí)行。

以上就是JavaScript在瀏覽器中的執(zhí)行機(jī)制的詳細(xì)內(nèi)容,更多關(guān)于JavaScript執(zhí)行機(jī)制的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評(píng)論