咱們開始之前,有個(gè)問題大家可以一起討論: JS是解釋語言還是編譯語言?
(JS)是一種解釋語言,有自己的編譯器形式,運(yùn)行在JS引擎中。
每個(gè)web瀏覽器都有自己的JS引擎形式,盡管目的一樣。Chrome 有 v8, Mozilla 有 spider monkey等,JS引擎只是將JS源代碼轉(zhuǎn)換成編譯器能夠理解的語言,然后執(zhí)行它。
JS 代碼運(yùn)行的環(huán)境構(gòu)成了執(zhí)行上下文,執(zhí)行上下文決定哪段代碼可以訪問變量、函數(shù)、對(duì)象等。
任何時(shí)候碼第一次運(yùn)行,或者當(dāng)代碼不在任何函數(shù)中時(shí),它都會(huì)進(jìn)入全局執(zhí)行上下文。在整個(gè)代碼執(zhí)行過程中只有一個(gè)全局執(zhí)行上下文。
對(duì)于瀏覽器全局執(zhí)行上下文,它做兩件事:
當(dāng)函數(shù)執(zhí)行時(shí),它就創(chuàng)建一個(gè)新的函數(shù)執(zhí)行上下文,可以有任意數(shù)量的函數(shù)執(zhí)行上下文。
瀏覽器中JS解器是單線程,同一時(shí)間只能干一件事。代碼中有一個(gè)全局的執(zhí)行上下文和無數(shù)個(gè)函數(shù)執(zhí)行上下文,那么他們是按什么順序執(zhí)行的呢?
這里就需要一個(gè) 執(zhí)行上下文棧 的概念了,JS引擎是通過創(chuàng)建執(zhí)行上下文棧來管理執(zhí)行上下文的。這里可以把執(zhí)行上下文棧描述為一個(gè)存著函數(shù)調(diào)用的棧結(jié)構(gòu),執(zhí)行順序遵循先進(jìn)后出的原則,也就是說一個(gè)函數(shù)的執(zhí)行上下文,在函數(shù)執(zhí)行完畢之后,會(huì)被移除執(zhí)行上下文棧。
每當(dāng)腳本在瀏覽器中加載時(shí),堆棧中的第一個(gè)元素就是全局執(zhí)行上下文。然而,當(dāng)一個(gè)函數(shù)執(zhí)行時(shí),將創(chuàng)建一個(gè)執(zhí)行上下文,并將其虛擬的放置在全局執(zhí)行上下文之上。函數(shù)一旦執(zhí)行完畢,就會(huì)從執(zhí)行堆棧中彈出并將控制權(quán)交給到它下面的上下文中。
咱們舉個(gè)例子,來模擬上述的過程:
步驟1:當(dāng)上述代碼加載到瀏覽器中時(shí),JS引擎創(chuàng)建一個(gè)全局執(zhí)行上下文(global execution context )并將其推入當(dāng)前執(zhí)行堆棧。
步驟2:假設(shè)最后執(zhí)行func1()調(diào)用,然后JS引擎為該函數(shù)創(chuàng)建一個(gè)新的執(zhí)行上下文(function execution context),并將其推到全局執(zhí)行上下文的頂部。
步驟3:在func1()中,咱們調(diào)用了func2(),因此JS引擎為該函數(shù)創(chuàng)建一個(gè)新的執(zhí)行上下文,并將其推到func1()執(zhí)行上下文的頂部。
步驟4:當(dāng)func2()函數(shù)結(jié)束時(shí),它的執(zhí)行上下文從當(dāng)前堆棧中彈出,控制權(quán)交給它下面的執(zhí)行上下文,即func1()函數(shù)的執(zhí)行上下文。
步驟5:當(dāng)func1()函數(shù)結(jié)束時(shí),它的執(zhí)行堆棧將從堆棧中刪除,控制權(quán)交給全局執(zhí)行上下文。執(zhí)行完所有代碼后,JS 引擎將從當(dāng)前堆棧中刪除全局執(zhí)行上下文。
執(zhí)行上下文主要有兩個(gè)階段。
函數(shù)創(chuàng)建時(shí)做的三件事:
1.首先,為用域鏈內(nèi)的每個(gè)函數(shù)或變量構(gòu)建到外部環(huán)境的連接。告訴執(zhí)行上下文它應(yīng)該包含什么,以及它應(yīng)該在哪里查找解析函數(shù)引用和變量值的方法。
2.接著,通過掃描作用鏈,創(chuàng)建一個(gè)環(huán)境記錄,其中全局上下文的創(chuàng)建和引用(web瀏覽器中的window)、變量、函數(shù)和函數(shù)參數(shù)都在內(nèi)存中完成。
3.最后,在第一步中創(chuàng)建的每個(gè)執(zhí)行上下文中確定this的值(對(duì)于全局執(zhí)行上下文,this指向的是window)。
因此,咱們可以將創(chuàng)建階段表示為
創(chuàng)建階段 = {
scopeChain: {
/* 作用域鏈解析 */
},
variableObject: {
/* arguments, 函數(shù)參數(shù), 內(nèi)部變量 等等*/
},
this: {},
}
variableObject: 初始化函數(shù)的參數(shù)variableObject,提升函數(shù)聲明和變量聲明。
scopeChain: 在執(zhí)行上下文的創(chuàng)建階段,作用域會(huì)在變量對(duì)象創(chuàng)建之后創(chuàng)建。作用域鏈本身包括變量對(duì)象。作用域負(fù)責(zé)解析變量,當(dāng)被要求解析變量的時(shí)候,會(huì)從代碼嵌套結(jié)構(gòu)的最內(nèi)層開始,如果在最內(nèi)層沒有找到對(duì)應(yīng)變量,則依次向父級(jí)作用域中進(jìn)行查找,直到尋找到最外層作用域。
this: 確定this的指向,這里需要注意的事this的值是在執(zhí)行的時(shí)候確定的,在定義的時(shí)候并不會(huì)確定。
這是代碼開始在創(chuàng)建階段形成的執(zhí)行上下文中運(yùn)行的階段,并逐行分配變量值。
在執(zhí)行開始時(shí),JS 引擎在其創(chuàng)建階段對(duì)象中尋找執(zhí)行函數(shù)的引用。如果不能在自己的作用域內(nèi)找到它,它將繼續(xù)向上查找,直到到達(dá)全局環(huán)境。
如果在全局環(huán)境中沒有找到引用,它將返回一個(gè)錯(cuò)誤。但是,如果找到了一個(gè)引用,并且函數(shù)執(zhí)行正確,那么這個(gè)特定函數(shù)的執(zhí)行上下文將從堆棧中彈出,JS 引擎將移動(dòng)到下一個(gè)函數(shù),在那里,它們的執(zhí)行上下文將被添加到堆棧中并執(zhí)行,依此類推。
咱們通過示例查看上面的兩個(gè)階段,以便更好地理解它。
1111
在創(chuàng)建階段,全局執(zhí)行上下文類似于這樣
globalExecutionObj = {
outerEnvironmentConnection: null,
variableObjectMapping: {
name: uninitialized,
title: undefined,
date: uninitialized,
func1: func,
},
this: window //Global Object
}
**注意:**上面,let (name)和const (date)定義的變量在創(chuàng)建階段沒有任何關(guān)聯(lián)的值,但是var (title)定義的變量會(huì)被設(shè)置為undefined。
這就是為什么咱們可以在聲明var定義的變量之前訪問它們**(雖然沒有定義)**,但是在聲明let和 const變量之前訪問它們時(shí),會(huì)得到一個(gè)引用錯(cuò)誤。
這就是咱們所說的變量提升,即所有使用var的變量聲明都被提升它們的局部作用域(在函數(shù)內(nèi)部聲明)或者全局作用域的頂部(在函數(shù)外部聲明的)。
在執(zhí)行階段,完成變量分配。所以全局執(zhí)行上下文在執(zhí)行階段類似如下:
globalExectutionObj = {
outerEnvironmentConnection: null,
variableObjectMapping: {
name: "overflowjs.com",
title: "Execution context",
date: "5 july 2019",
func1: pointer to function func1,
},
this: window //Global Object
}
**注意:**在執(zhí)行階段,如果JS引擎在源代碼中聲明位置找不到let變量的值,那么它將為其賦值undefined。
現(xiàn)在,當(dāng)func1執(zhí)行,就會(huì)生成一個(gè)新的函數(shù)執(zhí)行上下文,其創(chuàng)建階段類似如下:
func1ExecutionObj = {
outerEnvironmentConnection: Global,
variableObjectMapping: {
arguments: {
0: 10,
length: 1
},
num: 10,
author: undefined,
val: uninitialized,
func2: undefined
fixed: uninitialized
addFive: pointer to function addFive()
},
this: Global Object or undefined
}
在執(zhí)行階段類似如下:
func1ExecutionObj = {
outerEnvironmentConnection: Global,
variableObjectMapping: {
arguments: {
0: 10,
length: 1
},
num: 10,
author: "Deepak",
val: 3,
func2: pointer to function func2()
fixed: "Divine"
addFive: pointer to function addFive()
},
this: Global Object or undefined
}
函數(shù)執(zhí)行完成后,將更新全局環(huán)境。然后全局代碼完成,程序結(jié)束。
JavaScript中的作用域分為三種:
作用域最大的作用就是隔離變量或函數(shù),并控制他們的生命周期。作用域是在函數(shù)執(zhí)行上下文創(chuàng)建的時(shí)候定義好的,不是在函數(shù)執(zhí)行的時(shí)候定義的。
當(dāng)一個(gè)塊或者函數(shù)嵌套在另一個(gè)塊或函數(shù)中時(shí),就發(fā)生了作用域的嵌套。在當(dāng)前函數(shù)中如果JS引擎無法找到某個(gè)變量,就會(huì)往上級(jí)嵌套的作用域中去尋找,直到找到該變量或抵達(dá)全局作用域,這樣的鏈?zhǔn)疥P(guān)系成為作用域鏈(Scope Chain)。
來個(gè)例子演示一下:
var scope = 'global';
function checkscope(s) {
var scope = 'local scope';
function f() {
return scope;
}
return f();
}
checkScope('scope');
首先在checkscope函數(shù)聲明的時(shí)候,內(nèi)部會(huì)綁定一個(gè)[[scope]]的內(nèi)部屬性:
checkscope.[[scope]] = [
globalContext.VO
];
接著在checkScope函數(shù)在執(zhí)行之前,創(chuàng)建執(zhí)行上下文checkscopeContext,并推入執(zhí)行上下文棧:
// -> 初始化作用域鏈;
checkscopeContext = {
scope: [globalContext.VO],
}
// -> 創(chuàng)建變量對(duì)象
checkscopeContext = {
scope: [globalContext.VO],
VO = {
arguments: {
0: 'scope',
length: 1,
},
s: 'scope', // 傳入的參數(shù)
f: function f(),
scope: undefined, // 此時(shí)聲明的變量為undefined
},
}
// -> 將變量對(duì)象壓入作用域鏈的最頂端
checkscopeContext = {
scope: [VO, globalContext.VO],
VO = {
arguments: {
0: 'scope',
length: 1,
},
s: 'scope', // 傳入的參數(shù)
f: function f(),
scope: undefined, // 此時(shí)聲明的變量為undefined
},
}
執(zhí)行階段,修改變量對(duì)象里面對(duì)應(yīng)字段的值:
checkscopeContext = {
scope: [VO, globalContext.VO],
VO = {
arguments: {
0: 'scope',
length: 1,
},
s: 'scope', // 傳入的參數(shù)
f: pointer to function f(),
scope: 'local scope', // 變量賦值
}
}
在代碼執(zhí)行階段,會(huì)看到f函數(shù)的聲明代碼,給f函數(shù)綁定[[scope]]屬性:
f.[[scope]] = [
checkscopeContext.VO, // f函數(shù)的作用域還包括checkscope的變量對(duì)象
globalContext.VO
];
文本到這就結(jié)束了,希望對(duì)大伙有所幫助。
作者:DEEPAK GUPTA 譯者:前端小智 來源:overflowjs.com
原文:https://overflowjs.com/posts/Javascript-Execution-Context-and-Hoisting.html
篇文章的目的是為了讓你徹底理解 JavaScript 的執(zhí)行,如果你到本文最后還沒有理解,你可以揍我一頓。
無論你是 JavaScript 新手還是老手,無論你是在面試工作,還是只是做常規(guī)的開發(fā)工作,通常會(huì)發(fā)現(xiàn)給定幾行代碼,你需要知道要輸出什么以及以什么順序輸出 . 由于 JavaScript 是一種單線程語言,我們可以得出以下結(jié)論:
let a = '1';
console.log(a);
let b = '2';
console.log(b);
然而,JavaScript 實(shí)際上是這樣的:
setTimeout(function(){
console.log('start')
});
new Promise(function(resolve){
console.log('start for');
for(var i = 0; i < 10000; i++){
i == 99 && resolve();
}
}).then(function(){
console.log('start then')
});
console.log('end');
// Following the idea that JS executes in the order in which the statements appear, I confidently write down the output:
// start
// start for
// start then
// end
在 Chrome 上查看它是完全錯(cuò)誤的
JavaScript 是一種單線程語言。Web-worker 是在最新的 HTML5 中提出的,但 JavaScript 是單線程的核心保持不變。所以所有 JavaScript 版本的“多線程”都是用單線程模擬的,所有的 JavaScript 多線程都是紙老虎!
由于 JavaScript 是單線程的,它就像一個(gè)只有一個(gè)窗口的銀行??蛻粜枰灰慌抨?duì)辦理業(yè)務(wù)。
同樣,JavaScript 任務(wù)也需要一個(gè)一個(gè)地執(zhí)行。如果一項(xiàng)任務(wù)花費(fèi)的時(shí)間太長,則下一項(xiàng)也必須等待。
那么問題來了,如果我們想瀏覽新聞,但新聞中包含加載緩慢的超高清圖像,我們的網(wǎng)頁是否應(yīng)該一直卡住直到圖像完全顯示?所以聰明的程序員將任務(wù)分為兩類:
當(dāng)我們打開一個(gè)網(wǎng)站時(shí),頁面的渲染過程是很多同步任務(wù),比如渲染頁面骨架和頁面元素。
需要大量時(shí)間的任務(wù),比如加載圖片和音樂,都是異步任務(wù)。這部分有嚴(yán)格的文字定義,但本文的目的是以最小的學(xué)習(xí)成本徹底理解實(shí)現(xiàn)機(jī)制,所以我們用一張圖來說明:
文字要表達(dá)的內(nèi)容:
同步和異步任務(wù)去不同的執(zhí)行“地方”,同步任務(wù)去主線程,異步任務(wù)去事件表和注冊(cè)函數(shù)。
當(dāng)指定的事件完成時(shí),事件表將此函數(shù)移至事件隊(duì)列。
如果執(zhí)行后主線程中的任務(wù)為空,事件隊(duì)列會(huì)讀取相應(yīng)的函數(shù),進(jìn)入主線程執(zhí)行。
這個(gè)過程一遍又一遍地重復(fù),稱為事件循環(huán)。
我們?cè)趺粗乐骶€程棧是空的?JavaScript 引擎有一個(gè)監(jiān)控進(jìn)程,不斷檢查主線程堆棧是否為空,如果是,則檢查 Event Queue 以查看是否有任何函數(shù)等待調(diào)用。
說了這么多,不如直接寫一段代碼:
let data = [];
$.ajax({
url:www.javascript.com,
data:data,
success:() => {
console.log('success!');
}
})
console.log('end');
這是一個(gè)簡單的ajax請(qǐng)求代碼:
相信通過上面的文字和代碼,你對(duì)JS的執(zhí)行順序有了初步的了解。接下來,我們來看看進(jìn)階話題:setTimeout。
著名的 setTimeout 無需進(jìn)一步解釋。setTimeout 的第一印象是異步執(zhí)行可以延遲,我們經(jīng)常這樣實(shí)現(xiàn):
setTimeout(() => {
console.log(‘Delay 3 seconds’);
},3000)
當(dāng) setTimeout 用得越來越多時(shí),問題也出現(xiàn)了。有時(shí)函數(shù)會(huì)在 3 秒的書面延遲后 5 或 6 秒內(nèi)執(zhí)行。怎么了?
讓我們從一個(gè)例子開始:
setTimeout(() => {
task();
},3000)
console.log('console');
按照我們之前的結(jié)論,setTimeout是異步的,應(yīng)該先執(zhí)行console.log。
//console
//task()
去看看吧!這是正確的!然后我們修改之前的代碼:
復(fù)制setTimeout(() => {
task()
},3000)
sleep(10000000)
控制臺(tái)上的 task() 在 Chrome 中執(zhí)行需要超過 3 秒的時(shí)間。
此時(shí),我們需要重新思考setTimeout的定義。
先說上面的代碼是如何執(zhí)行的:
上述過程完成后,我們知道setTimeout是一個(gè)在指定時(shí)間后將任務(wù)添加到Event Queue(本例中為task())的函數(shù)。
而且,由于是單線程任務(wù),需要一個(gè)一個(gè)執(zhí)行,如果上一個(gè)任務(wù)耗時(shí)過長,我們只能等待。導(dǎo)致實(shí)際延遲超過 3 秒。
SetTimeout(fn,0) 是我們經(jīng)常遇到的另一個(gè)代碼??梢粤⒓赐瓿蓡??
SetTimeout (fn,0) 指定任務(wù)將在主線程上最早可用的空閑時(shí)間執(zhí)行。這意味著一旦堆棧中的所有同步任務(wù)完成并且堆棧為空,主線程將立即執(zhí)行。例如:
//code1
console.log('one');
setTimeout(() => {
console.log('two')
},0);
// result
// one
// two
//code2
console.log('one');
setTimeout(() => {
console.log('two')
},3000);
// result
// one
// ... 3s later
// two
關(guān)于 setTimeout 要補(bǔ)充的一點(diǎn)是,即使主線程是空的,0 毫秒實(shí)際上也是無法到達(dá)的。根據(jù) HTML 標(biāo)準(zhǔn),最小值為 4 毫秒。有興趣的同學(xué)可以自行了解。
說了 setTimeout,你不能錯(cuò)過它的孿生兄弟 setInterval。它們是相似的,只是后者是循環(huán)執(zhí)行。對(duì)于執(zhí)行順序,setInterval 將按指定的時(shí)間間隔將注冊(cè)的函數(shù)放入事件隊(duì)列中。如果上一個(gè)任務(wù)耗時(shí)過長,也需要等待。
唯一需要注意的是,對(duì)于 setInterval(fn,ms),我們已經(jīng)知道不是每 ms 秒執(zhí)行一次 fn,而是每 ms 秒進(jìn)入 Event Queue。一旦 setInterval 的回調(diào) fn 花費(fèi)的時(shí)間超過了延遲 ms,時(shí)間間隔就完全不可見了。請(qǐng)讀者細(xì)細(xì)品味這句話。
我們已經(jīng)看過傳統(tǒng)的計(jì)時(shí)器,然后,我們將探討 Promise 與 process.Nexttick(回調(diào))的性能。
Promise 的定義和功能這里就不介紹了,process.nexttick(回調(diào))類似于node.js 版本的“setTimeout”,在事件循環(huán)的下一次迭代中調(diào)用回調(diào)函數(shù)。
我們開始談?wù)掳?。除了廣義的同步和異步任務(wù),我們對(duì)任務(wù)有更詳細(xì)的定義:
不同類型的任務(wù)會(huì)進(jìn)入對(duì)應(yīng)的Event Queue。例如,setTimeout 和 setInterval 將進(jìn)入同一個(gè)事件隊(duì)列。
事件循環(huán)的順序決定了 JS 代碼的執(zhí)行順序。輸入整體代碼(宏任務(wù))后,第一個(gè)循環(huán)開始。然后,執(zhí)行所有微任務(wù)。然后再從宏任務(wù)開始,找一個(gè)任務(wù)隊(duì)列完成,然后,執(zhí)行所有的微任務(wù)。如果聽起來有點(diǎn)繞,我們用本文開頭的代碼來說明:
setTimeout(function() {
console.log('setTimeout');
})
new Promise(function(resolve) {
console.log('promise');
}).then(function() {
console.log('then');
})
console.log('console');
事件循環(huán)、宏任務(wù)和微任務(wù)的關(guān)系如下圖所示:
讓我們看一些更復(fù)雜的代碼,看看你是否真的了解 JS 的工作原理:
console.log('1');
setTimeout(function() {
console.log('2');
process.nextTick(function() {
console.log('3');
})
new Promise(function(resolve) {
console.log('4');
resolve();
}).then(function() {
console.log('5')
})
})
process.nextTick(function() {
console.log('6');
})
new Promise(function(resolve) {
console.log('7');
resolve();
}).then(function() {
console.log('8')
})
setTimeout(function() {
console.log('9');
process.nextTick(function() {
console.log('10');
})
new Promise(function(resolve) {
console.log('11');
resolve();
}).then(function() {
console.log('12')
})
})
第一輪事件循環(huán)流程分析如下:
整個(gè)腳本作為第一個(gè)宏任務(wù)進(jìn)入主線程,遇到console.log,打印1。
好了,第一輪事件循環(huán)正式結(jié)束,本輪結(jié)果輸出1,7,6,8。所以第二個(gè)時(shí)間循環(huán)從 setTimeout1 宏任務(wù)開始:
首先,print2。接下來是process.nexttick(),它也被分派到微任務(wù)事件隊(duì)列中,稱為process2。新的 Promise 立即執(zhí)行輸出 4,然后也被分發(fā)到微任務(wù)事件隊(duì)列中,記為 then2。
整個(gè)代碼,一共經(jīng)過了3次事件循環(huán),完整的輸出為1,7,6,8,2,4,3,5,9,11,10,12。
我們從一開始就說過 JavaScript 是單線程語言,無論什么新的框架和語法實(shí)現(xiàn)被稱為異步,實(shí)際上都是以同步的方式模擬的,所以牢牢掌握單線程很重要。
事件循環(huán)是實(shí)現(xiàn)異步 JavaScript 的一種方法,也是 JavaScript 的執(zhí)行機(jī)制。
在 Node.js、瀏覽器、Ringo 等不同的環(huán)境中執(zhí)行和運(yùn)行 JavaScript 是有很大區(qū)別的。雖然運(yùn)行多指 JavaScript 解析引擎,但它是統(tǒng)一的。
還有許多其他類型的微任務(wù)和宏任務(wù),例如 setImmediate,它們不進(jìn)行中介。
JavaScript 是一種單線程語言。事件循環(huán)是 JavaScript 的執(zhí)行機(jī)制。
牢牢把握兩個(gè)基本點(diǎn),以認(rèn)真學(xué)習(xí)JavaScript為中心,早日實(shí)現(xiàn)成為前端高手的偉大夢(mèng)想!
來源: WEB前端開發(fā)社區(qū)
言
在JavaScript的世界里,事件循環(huán)(Event Loop)是一個(gè)核心概念,它決定了JavaScript代碼的執(zhí)行順序,尤其是異步代碼。理解事件循環(huán)對(duì)于編寫高效、響應(yīng)迅速的JavaScript程序至關(guān)重要。本文將深入探討事件循環(huán)的原理,并通過實(shí)際代碼示例展示其在JavaScript編程中的應(yīng)用。
1. 理解事件循環(huán)
1.1 JavaScript的執(zhí)行模型
JavaScript有一個(gè)基于單線程的事件循環(huán)執(zhí)行模型。這意味著JavaScript代碼在一個(gè)單獨(dú)的線程上執(zhí)行,一次只能執(zhí)行一個(gè)任務(wù)。為了處理高延遲操作(如I/O),JavaScript采用了異步編程模型。
1.2 任務(wù)隊(duì)列
JavaScript中的任務(wù)分為兩種:宏觀任務(wù)(macrotasks)和微觀任務(wù)(microtasks)。宏觀任務(wù)包括例如setTimeout、setInterval、I/O操作等。微觀任務(wù)則包括例如Promise的回調(diào)、MutationObserver等。
1.3 事件循環(huán)的工作流程
事件循環(huán)的工作流程大致如下:
2. 事件循環(huán)的實(shí)際應(yīng)用
2.1 setTimeout和setInterval
示例代碼:
console.log('開始');
setTimeout(() => {
console.log('setTimeout');
}, 0);
console.log('結(jié)束');
輸出順序:開始 -> 結(jié)束 -> setTimeout
2.2 Promise和async/await
示例代碼:
console.log('開始');
Promise.resolve().then(() => {
console.log('Promise');
});
console.log('結(jié)束');
輸出順序:開始 -> 結(jié)束 -> Promise
2.3 宏觀任務(wù)和微觀任務(wù)的交互
示例代碼:
console.log('開始');
setTimeout(() => {
console.log('setTimeout 1');
Promise.resolve().then(() => {
console.log('Promise 1');
});
}, 0);
setTimeout(() => {
console.log('setTimeout 2');
}, 0);
Promise.resolve().then(() => {
console.log('Promise 2');
});
console.log('結(jié)束');
輸出順序:開始 -> 結(jié)束 -> Promise 2 -> setTimeout 1 -> Promise 1 -> setTimeout 2
3. 事件循環(huán)的性能考量
雖然事件循環(huán)使得JavaScript能夠高效地處理異步操作,但在編寫代碼時(shí),應(yīng)避免過多地使用微觀任務(wù),特別是在性能敏感的應(yīng)用中。此外,理解事件循環(huán)對(duì)于調(diào)試異步代碼也非常重要。
總結(jié)
事件循環(huán)是JavaScript中一個(gè)核心的概念,它決定了異步代碼的執(zhí)行順序。通過理解事件循環(huán),我們可以更有效地編寫和管理異步操作,從而創(chuàng)建響應(yīng)迅速且高效的JavaScript程序。在實(shí)際應(yīng)用中,無論是使用setTimeout、Promise,還是async/await,事件循環(huán)都扮演著至關(guān)重要的角色。然而,在享受事件循環(huán)帶來的便利的同時(shí),也需要注意性能和代碼結(jié)構(gòu)的問題,確保程序的高效運(yùn)行和可維護(hù)性。
*請(qǐng)認(rèn)真填寫需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。