Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
文的目的就是要保證你徹底弄懂javascript的執(zhí)行機(jī)制,如果讀完本文還不懂,可以揍我。
不論你是javascript新手還是老鳥,不論是面試求職,還是日常開發(fā)工作,我們經(jīng)常會(huì)遇到這樣的情況:給定的幾行代碼,我們需要知道其輸出內(nèi)容和順序。因?yàn)閖avascript是一門單線程語言,所以我們可以得出結(jié)論:
看到這里讀者要打人了:我難道不知道js是一行一行執(zhí)行的?還用你說?稍安勿躁,正因?yàn)閖s是一行一行執(zhí)行的,所以我們以為js都是這樣的:
let a = '1';
console.log(a);
let b = '2';
console.log(b);
然而實(shí)際上js是這樣的:
setTimeout(function(){
console.log('定時(shí)器開始啦')
});
new Promise(function(resolve){
console.log('馬上執(zhí)行for循環(huán)啦');
for(var i = 0; i < 10000; i++){
i == 99 && resolve();
}
}).then(function(){
console.log('執(zhí)行then函數(shù)啦')
});
console.log('代碼執(zhí)行結(jié)束');
依照js是按照語句出現(xiàn)的順序執(zhí)行這個(gè)理念,我自信的寫下輸出結(jié)果:
//"定時(shí)器開始啦"
//"馬上執(zhí)行for循環(huán)啦"
//"執(zhí)行then函數(shù)啦"
//"代碼執(zhí)行結(jié)束"
去chrome上驗(yàn)證下,結(jié)果完全不對,瞬間懵了,說好的一行一行執(zhí)行的呢?
我們真的要徹底弄明白javascript的執(zhí)行機(jī)制了。
javascript是一門單線程語言,在最新的HTML5中提出了Web-Worker,但javascript是單線程這一核心仍未改變。所以一切javascript版的"多線程"都是用單線程模擬出來的,一切javascript多線程都是紙老虎!
既然js是單線程,那就像只有一個(gè)窗口的銀行,客戶需要排隊(duì)一個(gè)一個(gè)辦理業(yè)務(wù),同理js任務(wù)也要一個(gè)一個(gè)順序執(zhí)行。如果一個(gè)任務(wù)耗時(shí)過長,那么后一個(gè)任務(wù)也必須等著。那么問題來了,假如我們想瀏覽新聞,但是新聞包含的超清圖片加載很慢,難道我們的網(wǎng)頁要一直卡著直到圖片完全顯示出來?因此聰明的程序員將任務(wù)分為兩類:
當(dāng)我們打開網(wǎng)站時(shí),網(wǎng)頁的渲染過程就是一大堆同步任務(wù),比如頁面骨架和頁面元素的渲染。而像加載圖片音樂之類占用資源大耗時(shí)久的任務(wù),就是異步任務(wù)。關(guān)于這部分有嚴(yán)格的文字定義,但本文的目的是用最小的學(xué)習(xí)成本徹底弄懂執(zhí)行機(jī)制,所以我們用導(dǎo)圖來說明:
導(dǎo)圖要表達(dá)的內(nèi)容用文字來表述的話:
我們不禁要問了,那怎么知道主線程執(zhí)行棧為空啊?js引擎存在monitoring process進(jìn)程,會(huì)持續(xù)不斷的檢查主線程執(zhí)行棧是否為空,一旦為空,就會(huì)去Event Queue那里檢查是否有等待被調(diào)用的函數(shù)。
說了這么多文字,不如直接一段代碼更直白:
let data = [];
$.ajax({
url:www.javascript.com,
data:data,
success:() => {
console.log('發(fā)送成功!');
}
})
console.log('代碼執(zhí)行結(jié)束');
上面是一段簡易的ajax請求代碼:
相信通過上面的文字和代碼,你已經(jīng)對js的執(zhí)行順序有了初步了解。接下來我們來研究進(jìn)階話題:setTimeout。
大名鼎鼎的setTimeout無需再多言,大家對他的第一印象就是異步可以延時(shí)執(zhí)行,我們經(jīng)常這么實(shí)現(xiàn)延時(shí)3秒執(zhí)行:
setTimeout(() => {
console.log('延時(shí)3秒');
},3000)
漸漸的setTimeout用的地方多了,問題也出現(xiàn)了,有時(shí)候明明寫的延時(shí)3秒,實(shí)際卻5,6秒才執(zhí)行函數(shù),這又咋回事啊?
先看一個(gè)例子:
setTimeout(() => {
task();
},3000)
console.log('執(zhí)行console');
根據(jù)前面我們的結(jié)論,setTimeout是異步的,應(yīng)該先執(zhí)行console.log這個(gè)同步任務(wù),所以我們的結(jié)論是:
//執(zhí)行console
//task()
復(fù)制代碼
去驗(yàn)證一下,結(jié)果正確! 然后我們修改一下前面的代碼:
setTimeout(() => {
task()
},3000)
sleep(10000000)
乍一看其實(shí)差不多嘛,但我們把這段代碼在chrome執(zhí)行一下,卻發(fā)現(xiàn)控制臺(tái)執(zhí)行task()需要的時(shí)間遠(yuǎn)遠(yuǎn)超過3秒,說好的延時(shí)三秒,為啥現(xiàn)在需要這么長時(shí)間啊?
這時(shí)候我們需要重新理解setTimeout的定義。我們先說上述代碼是怎么執(zhí)行的:
上述的流程走完,我們知道setTimeout這個(gè)函數(shù),是經(jīng)過指定時(shí)間后,把要執(zhí)行的任務(wù)(本例中為task())加入到Event Queue中,又因?yàn)槭菃尉€程任務(wù)要一個(gè)一個(gè)執(zhí)行,如果前面的任務(wù)需要的時(shí)間太久,那么只能等著,導(dǎo)致真正的延遲時(shí)間遠(yuǎn)遠(yuǎn)大于3秒。
我們還經(jīng)常遇到setTimeout(fn,0)這樣的代碼,0秒后執(zhí)行又是什么意思呢?是不是可以立即執(zhí)行呢?
答案是不會(huì)的,setTimeout(fn,0)的含義是,指定某個(gè)任務(wù)在主線程最早可得的空閑時(shí)間執(zhí)行,意思就是不用再等多少秒了,只要主線程執(zhí)行棧內(nèi)的同步任務(wù)全部執(zhí)行完成,棧為空就馬上執(zhí)行。舉例說明:
//代碼1
console.log('先執(zhí)行這里');
setTimeout(() => {
console.log('執(zhí)行啦')
},0);
復(fù)制代碼
//代碼2
console.log('先執(zhí)行這里');
setTimeout(() => {
console.log('執(zhí)行啦')
},3000);
代碼1的輸出結(jié)果是:
//先執(zhí)行這里
//執(zhí)行啦
代碼2的輸出結(jié)果是:
//先執(zhí)行這里
// ... 3s later
// 執(zhí)行啦
關(guān)于setTimeout要補(bǔ)充的是,即便主線程為空,0毫秒實(shí)際上也是達(dá)不到的。根據(jù)HTML的標(biāo)準(zhǔn),最低是4毫秒。有興趣的同學(xué)可以自行了解。
上面說完了setTimeout,當(dāng)然不能錯(cuò)過它的孿生兄弟setInterval。他倆差不多,只不過后者是循環(huán)的執(zhí)行。對于執(zhí)行順序來說,setInterval會(huì)每隔指定的時(shí)間將注冊的函數(shù)置入Event Queue,如果前面的任務(wù)耗時(shí)太久,那么同樣需要等待。
唯一需要注意的一點(diǎn)是,對于setInterval(fn,ms)來說,我們已經(jīng)知道不是每過ms秒會(huì)執(zhí)行一次fn,而是每過ms秒,會(huì)有fn進(jìn)入Event Queue。一旦setInterval的回調(diào)函數(shù)fn執(zhí)行時(shí)間超過了延遲時(shí)間ms,那么就完全看不出來有時(shí)間間隔了。這句話請讀者仔細(xì)品味。
傳統(tǒng)的定時(shí)器我們已經(jīng)研究過了,接著我們探究Promise與process.nextTick(callback)的表現(xiàn)。
Promise的定義和功能本文不再贅述,不了解的讀者可以學(xué)習(xí)一下阮一峰老師的Promise。而process.nextTick(callback)類似node.js版的"setTimeout",在事件循環(huán)的下一次循環(huán)中調(diào)用 callback 回調(diào)函數(shù)。
我們進(jìn)入正題,除了廣義的同步任務(wù)和異步任務(wù),我們對任務(wù)有更精細(xì)的定義:
不同類型的任務(wù)會(huì)進(jìn)入對應(yīng)的Event Queue,比如setTimeout和setInterval會(huì)進(jìn)入相同的Event Queue。
事件循環(huán)的順序,決定js代碼的執(zhí)行順序。進(jìn)入整體代碼(宏任務(wù))后,開始第一次循環(huán)。接著執(zhí)行所有的微任務(wù)。然后再次從宏任務(wù)開始,找到其中一個(gè)任務(wù)隊(duì)列執(zhí)行完畢,再執(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的執(zhí)行機(jī)制:
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)流程分析如下:
宏任務(wù)Event Queue | 微任務(wù)Event Queue |
setTimeout1 | process1 |
setTimeout2 | then1 |
好了,第一輪事件循環(huán)正式結(jié)束,這一輪的結(jié)果是輸出1,7,6,8。那么第二輪時(shí)間循環(huán)從setTimeout1宏任務(wù)開始:
宏任務(wù)Event Queue | 微任務(wù)Event Queue |
setTimeout2 | process2 |
then2 |
宏任務(wù)Event Queue | 微任務(wù)Event Queue |
process3 | |
then3 |
整段代碼,共進(jìn)行了三次事件循環(huán),完整的輸出為1,7,6,8,2,4,3,5,9,11,10,12。 (請注意,node環(huán)境下的事件監(jiān)聽依賴libuv與前端環(huán)境不完全相同,輸出順序可能會(huì)有誤差)
我們從最開頭就說javascript是一門單線程語言,不管是什么新框架新語法糖實(shí)現(xiàn)的所謂異步,其實(shí)都是用同步的方法去模擬的,牢牢把握住單線程這點(diǎn)非常重要。
事件循環(huán)是js實(shí)現(xiàn)異步的一種方法,也是js的執(zhí)行機(jī)制。
執(zhí)行和運(yùn)行有很大的區(qū)別,javascript在不同的環(huán)境下,比如node,瀏覽器,Ringo等等,執(zhí)行方式是不同的。而運(yùn)行大多指javascript解析引擎,是統(tǒng)一的。
微任務(wù)和宏任務(wù)還有很多種類,比如setImmediate等等,執(zhí)行都是有共同點(diǎn)的,有興趣的同學(xué)可以自行了解。
牢牢把握兩個(gè)基本點(diǎn),以認(rèn)真學(xué)習(xí)javascript為中心,早日實(shí)現(xiàn)成為前端高手的偉大夢想!
JavaScript是一種動(dòng)態(tài)的計(jì)算機(jī)編程語言。它是輕量級的,最常用作網(wǎng)頁的一部分,其實(shí)現(xiàn)允許客戶端腳本與用戶交互并創(chuàng)建動(dòng)態(tài)頁面。它是一種具有面向?qū)ο蠊δ艿慕忉屝途幊陶Z言。
Javascript和Java沒有任何關(guān)系,它們是不同的兩種語言(java是一種程序設(shè)計(jì)語言,javascript 是客戶端的腳本語言),只是名字上都有一個(gè)Java而已。
對了,在這里說一下,我目前是在職web前端開發(fā),如果你現(xiàn)在正在學(xué)習(xí)前端,了解前端,渴望成為一名合格的web前端開發(fā)工程師,在入門學(xué)習(xí)前端的過程當(dāng)中有遇見任何關(guān)于學(xué)習(xí)方法,學(xué)習(xí)路線,學(xué)習(xí)效率等方面的問題,都可以隨時(shí)關(guān)注并私信我:前端,我都會(huì)根據(jù)大家的問題給出針對性的建議,缺乏基礎(chǔ)入門的視頻教程也可以直接來找我,我這邊有最新的web前端基礎(chǔ)精講視頻教程, 還有我做web前端技術(shù)這段時(shí)間整理的一些學(xué)習(xí)手冊,面試題,開發(fā)工具,PDF文檔書籍教程,都可以直接分享給大家。
這三個(gè)要素共同構(gòu)成了Web開發(fā)的基礎(chǔ)。
HTML:頁面的結(jié)構(gòu)-標(biāo)題,正文,要包含的任何圖像
CSS:控制該頁面的外觀(這將用于自定義字體,背景顏色等)
JavaScript:不可思議的第三個(gè)元素。創(chuàng)建結(jié)構(gòu)(HTML)和美學(xué)氛圍(CSS)后,JavaScript使您的網(wǎng)站或項(xiàng)目充滿活力。
(上面這個(gè)作用是直接用的我的老師的課件,我可沒這么6懂這么多。他一個(gè)10多年開發(fā)經(jīng)驗(yàn)的資深程序員哈哈哈哈哈哈,有點(diǎn)想幫忙宣傳一下他的網(wǎng)課,但想想還是算了吧,感覺打廣告有點(diǎn)不好)
*********************************************一條華麗的分割線***************************************************
1. js程序必須寫在script標(biāo)簽中。
2. script:可以寫在網(wǎng)頁中的任何位置。
3. type=“text/javascript”:表示當(dāng)前的語言是javascript語言。這個(gè)屬性是可以省略的。
舉例:上代碼
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
</head>
<body>
<script type="text/javascript">
alert("出錯(cuò)啦")
</script>
</body>
</html>
拿代碼去運(yùn)行一下就知道了
創(chuàng)建一個(gè)js文件,在js文件中編寫js代碼。(外部文件中編寫js代碼就直接寫代碼就可以了,不用再添加script標(biāo)簽)
比如說在js目錄下面創(chuàng)建一個(gè) test.js文件 里面的代碼為alert(“出錯(cuò)啦!”)
舉例上代碼
a.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<script src="js/test.js" type="text/javascript" charset="UTF-8">
</script>
</body>
</html>
拿代碼去運(yùn)行一下就知道了
舉例上代碼:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<style type="text/css">
#box1{
width: 100px;
height: 100px;
background-color: red;
}
#box2{
width: 100px;
height: 100px;
background-color: blue;
}
</style>
</head>
<body>
<div id="box1">
</div>
<div id="box2">
</div>
<script type="text/javascript">
//目標(biāo):點(diǎn)擊box1時(shí),讓box2變顏色
var b1 = document.getElementById("box1")
b1.onclick=function(){
// 當(dāng)點(diǎn)擊b1的時(shí)候,執(zhí)行此處的代碼
document.getElementById("box2").style.backgroundColor="pink"
}
</script>
</body>
</html>
運(yùn)行效果拿去試試就知道了,點(diǎn)一下第一個(gè)小盒子
舉例上代碼
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<input type="text" name="tb1" id="tb1" value="" />+<input type="text" name="tb2" id="tb2" value="" /> =<input type="text" name="tb3" id="tb3" value="" />
<input type="button" id="btnjisuan" value="計(jì)算" onclick="add();" />
<a href="javascript:void(0);" onclick="bb();">騰訊</a>
<script type="text/javascript">
function add()
{
var v1=document.getElementById("tb1").value;
var v2=document.getElementById("tb2").value;
var v3=parseInt(v1) + parseInt(v2);
document.getElementById("tb3").value=v3;
}
function bb()
{
location.href="http://www.qq.com"; //通過js代碼實(shí)現(xiàn)頁面的跳轉(zhuǎn)
}
</script>
</body>
</html>
拿去運(yùn)行一個(gè)就知道了哈哈哈哈,這個(gè)學(xué)會(huì)了,下面那個(gè)就容易多啦!
*********************************************一條華麗的哈哈哈哈哈哈哈哈***************************************************
實(shí)現(xiàn)效果:點(diǎn)擊什么顏色代表的小框框,就會(huì)彈出穿啥衣服的 fairy
(哈哈哈哈 本人敲愛看這些美麗的事物哈哈哈哈)
自己可以下載一些圖片或者顏色漸變圖片用來做背景,放在img里面,可自己命名。基本格式如下圖:
上代碼:
網(wǎng)頁換膚.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<link rel="stylesheet" type="text/css" href="css/css2.css" id="btnlink"/>
</head>
<body>
<div id="box1">
<span id="s1" onclick="a1();">志玲</span><span id="s2" onclick="a2();">依林</span><span id="s3" onclick="a3();">昆凌</span>
</div>
<script type="text/javascript">
function a1()
{
document.getElementById("btnlink").href="css/css1.css";
}
function a2()
{
document.getElementById("btnlink").href="css/css2.css";
}
function a3()
{
document.getElementById("btnlink").href="css/css3.css";
}
</script>
</body>
</html>
css1.css
*{
margin: 0;
padding: 0;
}
html,body{
width:100%;
height: 100%;
}
body{
background-image: url(../img/blue.jpg);
background-repeat: repeat-x; /* 設(shè)置不重復(fù)平鋪 */
}
#box1{
width: 186px;
height: 60px;
background-color: white;
margin: 0 auto;
position: relative;
}
#s1{
width: 60px;
height: 60px;
background-color: blue;
display: inline-block;
margin: 1px;
cursor: pointer;
position: absolute; /* 子絕父相 */
left: 0;
top: 0;
}
#s2{
width: 60px;
height: 60px;
background-color:green;
display: inline-block;
margin: 1px;
cursor: pointer;
position: absolute;
left: 62px;
top: 0;
}
#s3{
width: 60px;
height: 60px;
background-color: pink;
display: inline-block;
margin: 1px;
cursor: pointer;
position: absolute;
right: 0;
top: 0;
}
css2.css
*{
margin: 0;
padding: 0;
}
html,body{
width:100%;
height: 100%;
}
body{
background-image: url(../img/green.jpg)
}
#box1{
width: 186px;
height: 60px;
background-color: white;
margin: 0 auto;
position: relative;
}
#s1{
width: 60px;
height: 60px;
background-color: blue;
display: inline-block;
margin: 1px;
cursor: pointer;
position: absolute; /* 子絕父相 */
left: 0;
top: 0;
}
#s2{
width: 60px;
height: 60px;
background-color:green;
display: inline-block;
margin: 1px;
cursor: pointer;
position: absolute;
left: 62px;
top: 0;
}
#s3{
width: 60px;
height: 60px;
background-color: pink;
display: inline-block;
margin: 1px;
cursor: pointer;
position: absolute;
right: 0;
top: 0;
}
css3.css
*{
margin: 0;
padding: 0;
}
html,body{
width:100%;
height: 100%;
}
body{
background-image: url(../img/pink.jpg)
}
#box1{
width: 186px;
height: 60px;
background-color: white;
margin: 0 auto;
position: relative;
}
#s1{
width: 60px;
height: 60px;
background-color: blue;
display: inline-block;
margin: 1px;
cursor: pointer;
position: absolute; /* 子絕父相 */
left: 0;
top: 0;
}
#s2{
width: 60px;
height: 60px;
background-color:green;
display: inline-block;
margin: 1px;
cursor: pointer;
position: absolute;
left: 62px;
top: 0;
}
#s3{
width: 60px;
height: 60px;
background-color: pink;
display: inline-block;
margin: 1px;
cursor: pointer;
position: absolute;
right: 0;
top: 0;
}
一些很基礎(chǔ)的東西,要是寫起來那就太多了,很多不常用的,到了我們需要它的時(shí)候谷歌和百度就行了。
由于時(shí)間關(guān)系,暫時(shí)更到這里。
原文鏈接:https://link.zhihu.com/?target=https%3A//blog.csdn.net/hanhanwanghaha/article/details/109188646
作者:我一個(gè)超級無敵可愛的人鴨
出處:CSDN
介:本文是一個(gè) V8 編譯原理知識(shí)的介紹文章,旨在讓大家感性的了解 JavaScript 在 V8 中的解析過程。
作者 | 子弈
來源 | 阿里技術(shù)公眾號
本文是一個(gè) V8 編譯原理知識(shí)的介紹文章,旨在讓大家感性的了解 JavaScript 在 V8 中的解析過程。本文主要的撰寫流程如下:
本文僅代表個(gè)人觀點(diǎn),文中若有錯(cuò)誤歡迎指正。
大家可能一直疑惑的問題:JavaScript 是一門解釋型語言嗎?要了解這個(gè)問題,首先需要初步了解什么是解釋器和編譯器以及它們的特點(diǎn)是什么。
解釋器的作用是將某種語言編寫的源程序作為輸入,將該源程序執(zhí)行的結(jié)果作為輸出,例如 Perl、Scheme、APL 等都是使用解釋器進(jìn)行轉(zhuǎn)換執(zhí)行:
編譯器的設(shè)計(jì)是一個(gè)非常龐大和復(fù)雜的軟件系統(tǒng)設(shè)計(jì),在真正設(shè)計(jì)的時(shí)候需要解決兩個(gè)相對重要的問題:
中間表示(IR)
中間表示(Intermediate Representation,IR)是程序結(jié)構(gòu)的一種表現(xiàn)方式,它會(huì)比抽象語法樹(Abstract Syntax Tree,AST)更加接近匯編語言或者指令集,同時(shí)也會(huì)保留源程序中的一些高級信息,具體作用包括:
優(yōu)化編譯器
IR 本身可以做到多趟迭代從而優(yōu)化源程序,在每一趟迭代的過程中可以研究代碼并記錄優(yōu)化的細(xì)節(jié),方便后續(xù)的迭代查找并利用這些優(yōu)化信息,最終可以高效輸出更優(yōu)的目標(biāo)程序:
優(yōu)化器可以對 IR 進(jìn)行一趟或者多趟處理,從而生成更快執(zhí)行速度或者更小體積的目標(biāo)程序(例如找到循環(huán)中不變的計(jì)算并對其進(jìn)行優(yōu)化從而減少運(yùn)算次數(shù)),也可能用于產(chǎn)生更少異常或者更低功耗的目標(biāo)程序。除此之外,前端和后端內(nèi)部還可以細(xì)分為多個(gè)處理步驟,具體如下圖所示:
解釋器和編譯器的具體特性比較如下所示:
需要注意早期的 Web 前端要求頁面的啟動(dòng)速度快,因此采用解釋執(zhí)行的方式,但是頁面在運(yùn)行的過程中性能相對較低。為了解決這個(gè)問題,需要在運(yùn)行時(shí)對 JavaScript 代碼進(jìn)行優(yōu)化,因此在 JavaScript 的解析引擎中引入了 JIT 技術(shù)。
JIT (Just In Time)編譯器是一種動(dòng)態(tài)編譯技術(shù),相對于傳統(tǒng)編譯器而言,最大的區(qū)別在于編譯時(shí)和運(yùn)行時(shí)不分離,是一種在運(yùn)行的過程中對代碼進(jìn)行動(dòng)態(tài)編譯的技術(shù)。
為了解決 JavaScript 在運(yùn)行時(shí)性能較慢的問題,可以通過引入 JIT 技術(shù),并采用混合動(dòng)態(tài)編譯的方式來提升 JavaScript 的運(yùn)行性能,具體思路如下所示:
采用上述編譯框架后,可以使得 JavaScript 語言:
V8 是一個(gè)開源的 JavaScript 虛擬機(jī),目前主要用在 Chrome 瀏覽器(包括開源的 Chromium)以及 Node.js 中,核心功能是用于解析和執(zhí)行 JavaScript 語言。為了解決早期 JavaScript 運(yùn)行性能差的問題,V8 經(jīng)歷了多個(gè)歷史的編譯框架衍變之后(感興趣的同學(xué)可以了解一下早期的 V8 編譯框架設(shè)計(jì)),引入混合動(dòng)態(tài)編譯的技術(shù)來解決問題,具體詳細(xì)的編譯框架如下所示:
Ignition 的主要作用是將 AST 轉(zhuǎn)換成 Bytecode(字節(jié)碼,中間表示)。在運(yùn)行的過程中,還會(huì)使用類型反饋(TypeFeedback)技術(shù)并計(jì)算熱點(diǎn)代碼(HotSpot,重復(fù)被運(yùn)行的代碼,可以是方法也可以是循環(huán)體),最終交給 TurboFan 進(jìn)行動(dòng)態(tài)運(yùn)行時(shí)的編譯優(yōu)化。Ignition 的解釋執(zhí)行流程如下所示:
在字節(jié)碼解釋執(zhí)行的過程中,會(huì)將需要進(jìn)行性能優(yōu)化的運(yùn)行時(shí)信息指向?qū)?yīng)的 Feedback Vector(反饋向量,之前也被稱為 Type Feedback Vector),F(xiàn)eeback Vector 中會(huì)包含根據(jù)內(nèi)聯(lián)緩存(Inline Cache,IC)來存儲(chǔ)的多種類型的插槽(Feedback Vector Slot)信息,例如 BinaryOp 插槽(二進(jìn)制操作結(jié)果的數(shù)據(jù)類型)、Invocation Count(函數(shù)的調(diào)用次數(shù))以及 Optimized Code 信息等。
這里不會(huì)過多講解每個(gè)執(zhí)行流程的細(xì)節(jié)問題。
TurboFan 利用了 JIT 編譯技術(shù),主要作用是對 JavaScript 代碼進(jìn)行運(yùn)行時(shí)編譯優(yōu)化,具體的流程如下所示:
圖片出處 An Introduction to Speculative Optimization in V8。
需要注意 Profiling Feedback 部分,這里主要提供 Ignition 解釋執(zhí)行過程中生成的運(yùn)行時(shí)反饋向量信息 Feedback Vector ,Turbofan 會(huì)結(jié)合字節(jié)碼以及反饋向量信息生成圖示(數(shù)據(jù)結(jié)構(gòu)中的圖結(jié)構(gòu)),并將圖傳遞給前端部分,之后會(huì)根據(jù)反饋向量信息對代碼進(jìn)行優(yōu)化和去優(yōu)化。
這里的去優(yōu)化是指讓代碼回退到 Ignition 進(jìn)行解釋執(zhí)行,去優(yōu)化本質(zhì)是因?yàn)闄C(jī)器碼已經(jīng)不能滿足運(yùn)行訴求,例如一個(gè)變量從 string 類型轉(zhuǎn)變成 number 類型,機(jī)器碼編譯的是 string 類型,此時(shí)已經(jīng)無法再滿足運(yùn)行訴求,因此 V8 會(huì)執(zhí)行去優(yōu)化動(dòng)作,將代碼回退到 Ignition 進(jìn)行解釋執(zhí)行。
在了解 V8 的編譯原理之后,接下來需要使用 V8 的調(diào)試工具來具體查看 JavaScript 的編譯和運(yùn)行信息,從而加深我們對 V8 的編譯過程認(rèn)知。
如果想了解 JavaScript 在 V8 中的編譯時(shí)和運(yùn)行時(shí)信息,可以使用調(diào)試工具 D8。D8 是 V8 引擎的命令行 Shell,可以查看 AST 生成、中間代碼 ByteCode、優(yōu)化代碼、反優(yōu)化代碼、優(yōu)化編譯器的統(tǒng)計(jì)數(shù)據(jù)、代碼的 GC 等信息。D8 的安裝方式有很多,如下所示:
本文使用方法三安裝 v8-debug 工具,安裝完成后執(zhí)行 v8-debug --help 可以查看有哪些命令:
# 執(zhí)行 help 命令查看支持的參數(shù)
v8-debug --help
Synopsis:
shell [options] [--shell] [<file>...]
d8 [options] [-e <string>] [--shell] [[--module|--web-snapshot] <file>...]
-e execute a string in V8
--shell run an interactive JavaScript shell
--module execute a file as a JavaScript module
--web-snapshot execute a file as a web snapshot
SSE3=1 SSSE3=1 SSE4_1=1 SSE4_2=1 SAHF=1 AVX=1 AVX2=1 FMA3=1 BMI1=1 BMI2=1 LZCNT=1 POPCNT=1 ATOM=0
The following syntax for options is accepted (both '-' and '--' are ok):
--flag (bool flags only)
--no-flag (bool flags only)
--flag=value (non-bool flags only, no spaces around '=')
--flag value (non-bool flags only)
-- (captures all remaining args in JavaScript)
Options:
# 打印生成的字節(jié)碼
--print-bytecode (print bytecode generated by ignition interpreter)
type: bool default: --noprint-bytecode
# 跟蹤被優(yōu)化的信息
--trace-opt (trace optimized compilation)
type: bool default: --notrace-opt
--trace-opt-verbose (extra verbose optimized compilation tracing)
type: bool default: --notrace-opt-verbose
--trace-opt-stats (trace optimized compilation statistics)
type: bool default: --notrace-opt-stats
# 跟蹤去優(yōu)化的信息
--trace-deopt (trace deoptimization)
type: bool default: --notrace-deopt
--log-deopt (log deoptimization)
type: bool default: --nolog-deopt
--trace-deopt-verbose (extra verbose deoptimization tracing)
type: bool default: --notrace-deopt-verbose
--print-deopt-stress (print number of possible deopt points)
# 查看編譯生成的 AST
--print-ast (print source AST)
type: bool default: --noprint-ast
# 查看編譯生成的代碼
--print-code (print generated code)
type: bool default: --noprint-code
# 查看優(yōu)化后的代碼
--print-opt-code (print optimized code)
type: bool default: --noprint-opt-code
# 允許在源代碼中使用 V8 提供的原生 API 語法
--allow-natives-syntax (allow natives syntax)
type: bool default: --noallow-natives-syntax
我們編寫一個(gè) index.js 文件,在文件中寫入 JavaScript 代碼,執(zhí)行一個(gè)簡單的 add 函數(shù):
function add(x, y) {
return x + y
}
console.log(add(1, 2));
使用 --print-ast 參數(shù)可以打印 add 函數(shù)的 AST 信息:
v8-debug --print-ast ./index.js
[generating bytecode for function: ]
--- AST ---
FUNC at 0
. KIND 0
. LITERAL ID 0
. SUSPEND COUNT 0
. NAME ""
. INFERRED NAME ""
. DECLS
. . FUNCTION "add" = function add
. EXPRESSION STATEMENT at 41
. . ASSIGN at -1
. . . VAR PROXY local[0] (0x7fb8c080e630) (mode = TEMPORARY, assigned = true) ".result"
. . . CALL
. . . . PROPERTY at 49
. . . . . VAR PROXY unallocated (0x7fb8c080e6f0) (mode = DYNAMIC_GLOBAL, assigned = false) "console"
. . . . . NAME log
. . . . CALL
. . . . . VAR PROXY unallocated (0x7fb8c080e470) (mode = VAR, assigned = true) "add"
. . . . . LITERAL 1
. . . . . LITERAL 2
. RETURN at -1
. . VAR PROXY local[0] (0x7fb8c080e630) (mode = TEMPORARY, assigned = true) ".result"
[generating bytecode for function: add]
--- AST ---
FUNC at 12
. KIND 0
. LITERAL ID 1
. SUSPEND COUNT 0
. NAME "add"
. PARAMS
. . VAR (0x7fb8c080e4d8) (mode = VAR, assigned = false) "x"
. . VAR (0x7fb8c080e580) (mode = VAR, assigned = false) "y"
. DECLS
. . VARIABLE (0x7fb8c080e4d8) (mode = VAR, assigned = false) "x"
. . VARIABLE (0x7fb8c080e580) (mode = VAR, assigned = false) "y"
. RETURN at 25
. . ADD at 34
. . . VAR PROXY parameter[0] (0x7fb8c080e4d8) (mode = VAR, assigned = false) "x"
. . . VAR PROXY parameter[1] (0x7fb8c080e580) (mode = VAR, assigned = false) "y"
我們以圖形化的方式來描述生成的 AST 樹:
VAR PROXY 節(jié)點(diǎn)在真正的分析階段會(huì)連接到對應(yīng)地址的 VAR 節(jié)點(diǎn)。
AST 會(huì)經(jīng)過 Ignition 解釋器的 BytecodeGenerator 函數(shù)生成字節(jié)碼(中間表示),我們可以通過 --print-bytecode 參數(shù)來打印字節(jié)碼信息:
v8-debug --print-bytecode ./index.js
[generated bytecode for function: (0x3ab2082933f5 <SharedFunctionInfo>)]
Bytecode length: 43
Parameter count 1
Register count 6
Frame size 48
OSR nesting level: 0
Bytecode Age: 0
0x3ab2082934be @ 0 : 13 00 LdaConstant [0]
0x3ab2082934c0 @ 2 : c3 Star1
0x3ab2082934c1 @ 3 : 19 fe f8 Mov <closure>, r2
0x3ab2082934c4 @ 6 : 65 52 01 f9 02 CallRuntime [DeclareGlobals], r1-r2
0x3ab2082934c9 @ 11 : 21 01 00 LdaGlobal [1], [0]
0x3ab2082934cc @ 14 : c2 Star2
0x3ab2082934cd @ 15 : 2d f8 02 02 LdaNamedProperty r2, [2], [2]
0x3ab2082934d1 @ 19 : c3 Star1
0x3ab2082934d2 @ 20 : 21 03 04 LdaGlobal [3], [4]
0x3ab2082934d5 @ 23 : c1 Star3
0x3ab2082934d6 @ 24 : 0d 01 LdaSmi [1]
0x3ab2082934d8 @ 26 : c0 Star4
0x3ab2082934d9 @ 27 : 0d 02 LdaSmi [2]
0x3ab2082934db @ 29 : bf Star5
0x3ab2082934dc @ 30 : 63 f7 f6 f5 06 CallUndefinedReceiver2 r3, r4, r5, [6]
0x3ab2082934e1 @ 35 : c1 Star3
0x3ab2082934e2 @ 36 : 5e f9 f8 f7 08 CallProperty1 r1, r2, r3, [8]
0x3ab2082934e7 @ 41 : c4 Star0
0x3ab2082934e8 @ 42 : a9 Return
Constant pool (size = 4)
0x3ab208293485: [FixedArray] in OldSpace
- map: 0x3ab208002205 <Map>
- length: 4
0: 0x3ab20829343d <FixedArray[2]>
1: 0x3ab208202741 <String[7]: #console>
2: 0x3ab20820278d <String[3]: #log>
3: 0x3ab208003f09 <String[3]: #add>
Handler Table (size = 0)
Source Position Table (size = 0)
[generated bytecode for function: add (0x3ab20829344d <SharedFunctionInfo add>)]
Bytecode length: 6
// 接受 3 個(gè)參數(shù), 1 個(gè)隱式的 this,以及顯式的 x 和 y
Parameter count 3
Register count 0
// 不需要局部變量,因此幀大小為 0
Frame size 0
OSR nesting level: 0
Bytecode Age: 0
0x3ab2082935f6 @ 0 : 0b 04 Ldar a1
0x3ab2082935f8 @ 2 : 39 03 00 Add a0, [0]
0x3ab2082935fb @ 5 : a9 Return
Constant pool (size = 0)
Handler Table (size = 0)
Source Position Table (size = 0)
add 函數(shù)主要包含以下 3 個(gè)字節(jié)碼序列:
// Load Accumulator Register
// 加載寄存器 a1 的值到累加器中
Ldar a1
// 讀取寄存器 a0 的值并累加到累加器中,相加之后的結(jié)果會(huì)繼續(xù)放在累加器中
// [0] 指向 Feedback Vector Slot,Ignition 會(huì)收集值的分析信息,為后續(xù)的 TurboFan 優(yōu)化做準(zhǔn)備
Add a0, [0]
// 轉(zhuǎn)交控制權(quán)給調(diào)用者,并返回累加器中的值
Return
這里 Ignition 的解釋執(zhí)行這些字節(jié)碼采用的是一地址指令結(jié)構(gòu)的寄存器架構(gòu)。
關(guān)于更多字節(jié)碼的信息可查看 Understanding V8’s Bytecode。
JavaScript 是弱類型語言,不會(huì)像強(qiáng)類型語言那樣需要限定函數(shù)調(diào)用的形參數(shù)據(jù)類型,而是可以非常靈活的傳入各種類型的參數(shù)進(jìn)行處理,如下所示:
function add(x, y) {
// + 操作符是 JavaScript 中非常復(fù)雜的一個(gè)操作
return x + y
}
add(1, 2);
add('1', 2);
add(, 2);
add(undefined, 2);
add([], 2);
add({}, 2);
add([], {});
為了可以進(jìn)行 + 操作符運(yùn)算,在底層執(zhí)行的時(shí)候往往需要調(diào)用很多 API,比如 ToPrimitive(判斷是否是對象)、ToString、ToNumber 等,將傳入的參數(shù)進(jìn)行符合 + 操作符的數(shù)據(jù)轉(zhuǎn)換處理。
在這里 V8 會(huì)對 JavaScript 像強(qiáng)類型語言那樣對形參 x 和 y 進(jìn)行推測,這樣就可以在運(yùn)行的過程中排除一些副作用分支代碼,同時(shí)這里也會(huì)預(yù)測代碼不會(huì)拋出異常,因此可以對代碼進(jìn)行優(yōu)化,從而達(dá)到最高的運(yùn)行性能。在 Ignition 中通過字節(jié)碼來收集反饋信息(Feedback Vector),如下所示:
為了查看 add 函數(shù)的運(yùn)行時(shí)反饋信息,我們可以通過 V8 提供的 Native API 來打印 add 函數(shù)的運(yùn)行時(shí)信息,具體如下所示:
function add(x, y) {
return x + y
}
// 注意這里默認(rèn)采用了 ClosureFeedbackCellArray,為了查看效果,強(qiáng)制開啟 FeedbackVector
// 更多信息查看: A lighter V8:https://v8.dev/blog/v8-lite
%EnsureFeedbackVectorForFunction(add);
add(1, 2);
// 打印 add 詳細(xì)的運(yùn)行時(shí)信息
%DebugPrint(add);
通過 --allow-natives-syntax 參數(shù)可以在 JavaScript 中調(diào)用 %DebugPrint 底層 Native API(更多 API 可以查看 V8 的 runtime.h 頭文件):
點(diǎn)擊鏈接查看原文V8 編譯淺談,關(guān)注公眾號【阿里技術(shù)】獲取更多福利!
版權(quán)聲明:本文內(nèi)容由阿里云實(shí)名注冊用戶自發(fā)貢獻(xiàn),版權(quán)歸原作者所有,阿里云開發(fā)者社區(qū)不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。具體規(guī)則請查看《阿里云開發(fā)者社區(qū)用戶服務(wù)協(xié)議》和《阿里云開發(fā)者社區(qū)知識(shí)產(chǎn)權(quán)保護(hù)指引》。如果您發(fā)現(xiàn)本社區(qū)中有涉嫌抄襲的內(nèi)容,填寫侵權(quán)投訴表單進(jìn)行舉報(bào),一經(jīng)查實(shí),本社區(qū)將立刻刪除涉嫌侵權(quán)內(nèi)容。
*請認(rèn)真填寫需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。