前言
我們天天寫業(yè)務,公司小吟宦,業(yè)務簡單篮洁,經(jīng)驗不值錢,出來面試被說沒亮點殃姓,寫個業(yè)務都要問原理袁波,如果沒答好,就開始以下場景:
面試官:“你有什么要問我的嗎蜗侈?”
你:“額篷牌,我,我沒什么要問的踏幻。枷颊。〗斜叮”
不偷卧,其實你不是這樣子的豺瘤,你不想這么憋屈的離開吆倦,你不想就這樣辛辛苦苦跑過來被兩三句話打發(fā)掉!
所以坐求,開啟絕地反擊模式:
面試官:“你有什么要問我的嗎蚕泽?”
你:“js的運行時是怎么樣的?”
面試官:“我桥嗤。须妻。》毫欤”
運行時解析
如下代碼
var a = 1
let b = 2
const c = {k: 3}
function d () {
console.log(a)
var a = 11
let b = 22
dd()
}
function dd () {
console.log(b)
}
d() // 輸出啥荒吏? undefined 2
如果你是js老手, 一看d()執(zhí)行結(jié)果就知道是a變量提升了打印undefined,dd方法執(zhí)行打印2渊鞋,那a為什么會提升呢, 怎么不是打印外面的變量a = 1呢绰更?b怎么不打印22呢?想要弄清楚這些問題锡宋,就要了解js的執(zhí)行機制了儡湾。
js運行時有兩個階段:編譯階段、執(zhí)行階段执俩。
編譯階段:js通過編譯生成執(zhí)行上下文和可執(zhí)行代碼兩部分徐钠。
執(zhí)行階段:執(zhí)行可執(zhí)行代碼,輸出結(jié)果役首。
可以理解如下圖
執(zhí)行上下文是啥尝丐?可執(zhí)行代碼又是啥显拜?
執(zhí)行上下文是 JavaScript 執(zhí)行一段代碼時的運行環(huán)境,比如調(diào)用一個函數(shù)爹袁,就會進入這個函數(shù)的執(zhí)行上下文讼油,確定該函數(shù)在執(zhí)行期間用到的諸如 this、變量呢簸、對象以及函數(shù)等矮台。
可執(zhí)行代碼就是指js引擎可以執(zhí)行的代碼,這些代碼可以是【字節(jié)碼根时、機器碼】瘦赫。我們寫的js代碼它看不懂,也不會執(zhí)行蛤迎,它要先把js編譯成字節(jié)碼确虱、機器碼才行。
其實不管是什么高級語言代碼都要編譯才能執(zhí)行替裆,后面再說校辩。
了解了這兩個概念還不夠,執(zhí)行上下文里面有哪些內(nèi)容辆童?可以如下圖表示
看上圖宜咒,這是編譯階段的輸出,可以了解到一些重要信息:
變量環(huán)境:執(zhí)行上下文中var 聲明的變量把鉴,且賦值默認值undefined故黑。
詞法環(huán)境:執(zhí)行上下文中l(wèi)et const聲明的變量,這是解決變量提升問題庭砍,重點這是在es6加入的场晶,es5并沒有詞法環(huán)境。
outer: 外部引用怠缸,其實在每個執(zhí)行上下文中诗轻,都包含了一個外部引用,用來指向外部的執(zhí)行上下文揭北,我們把這個外部引用稱為 outer扳炬。在js中有全局執(zhí)行上下文、函數(shù)上下文之說罐呼,每個上下文的外部引用都是在編譯時決定的鞠柄,這個和代碼的編譯時的位置有關,也稱為詞法作用域嫉柴。詞法作用域就是指作用域是由代碼中函數(shù)聲明的位置來決定的厌杜,所以詞法作用域是靜態(tài)的作用域,通過它就能夠預測代碼在執(zhí)行過程中如何查找標識符。理解可以參考下圖
this: this是一套和作用域無關的獨立機制瞧壮,這個你可以另外找到相關文章深入理解。
變量提升其實就是函數(shù)運行的編譯階段匙握,把所有var聲明的變量統(tǒng)一提升到該作用域“頂部”咆槽, 并賦默認值undefined。
所以執(zhí)行d()函數(shù)的時候圈纺,相當于以下流程,先編譯再執(zhí)行
var a = undefined
console.log(a)
a = 11
而dd()執(zhí)行的時候其實就是按照上面說的秦忿,由于dd上下文沒有聲明b變量,根據(jù)作用域鏈查找外部引用蛾娶,直到首次找到b灯谣,而作用域在編譯階段就確定了外部引用。具體可以參考下圖:
所以蛔琅,就例子而言無論d方法還是dd方法胎许,它們的外部引用都是指向全局上下文,在執(zhí)行階段罗售,如果發(fā)現(xiàn)函數(shù)作用域沒有變量聲明就會沿著作用域往外部引用查找辜窑。
總結(jié)一下
1、js運行時有兩個階段:編譯階段 和 執(zhí)行階段
2寨躁、變量提升的根本原因是js在編譯階段確定的
3穆碎、執(zhí)行上下文有變量環(huán)境與詞法環(huán)境,變量環(huán)境存儲var聲明的變量朽缎,詞法環(huán)境存儲let cosnt聲明的變量惨远。
4谜悟、每個作用域的外部引用在編譯階段根據(jù)代碼的位置確定话肖。
v8引擎角度看javascript運行時
上面說到js運行時有編譯階段 和 執(zhí)行階段兩個階段,那么再從js引擎的角度來看葡幸,這是怎么一回事最筒。
首先先了解一些概念:
編譯器和解釋器
之所以存在編譯器和解釋器,是因為機器不能直接理解我們所寫的代碼蔚叨,所以在執(zhí)行程序之前床蜘,需要將我們所寫的代碼“翻譯”成機器能讀懂的機器語言。
按語言的執(zhí)行流程蔑水,可以把語言劃分為編譯型語言和解釋型語言邢锯。編譯型語言在程序執(zhí)行之前,需要經(jīng)過編譯器的編譯過程搀别,并且編譯之后會直接保留機器能讀懂的二進制文件丹擎,這樣每次運行程序時,都可以直接運行該二進制文件,而不需要再次重新編譯了蒂培。比如 C/C++再愈、GO 等都是編譯型語言。而由解釋型語言編寫的程序护戳,在每次運行時都需要通過解釋器對程序進行動態(tài)解釋和執(zhí)行翎冲。比如 Python、JavaScript 等都屬于解釋型語言媳荒。
了解上面的概念之后,v8引擎就是走的基于解釋器的路線钳枕,但是又與它不同檐春,是一個改進版。具體如下圖
從圖中可以看出v8是解釋器么伯、編譯器一起用了的疟暖。
通常,如果有一段第一次執(zhí)行的字節(jié)碼田柔,解釋器 Ignition 會逐條解釋執(zhí)行俐巴。解釋器 Ignition 除了負責生成字節(jié)碼之外,它還有另外一個作用硬爆,就是解釋執(zhí)行字節(jié)碼欣舵。在 Ignition 執(zhí)行字節(jié)碼的過程中,如果發(fā)現(xiàn)有熱點代碼(HotSpot)缀磕,比如一段代碼被重復執(zhí)行多次缘圈,這種就稱為熱點代碼,那么后臺的編譯器 TurboFan 就會把該段熱點的字節(jié)碼編譯為高效的機器碼袜蚕,然后當再次執(zhí)行這段被優(yōu)化的代碼時糟把,只需要執(zhí)行編譯后的機器碼就可以了,這樣就大大提升了代碼的執(zhí)行效率牲剃。
回到上面的js運行時兩個階段遣疯,編譯階段和執(zhí)行階段,編譯階段就是源碼 ->AST -> 字節(jié)碼凿傅。執(zhí)行階段就是基于字節(jié)碼缠犀、機器碼執(zhí)行。
最后
好了聪舒,以上就是javascript運行時的內(nèi)容辨液,了解這一過程希望對你開發(fā)過程有所幫助。