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

一、JS代碼執(zhí)行流程

JS的執(zhí)行機(jī)制:先編譯益缎,再執(zhí)行谜慌。js代碼在編譯階段,會創(chuàng)建執(zhí)行上下文链峭,變量和函數(shù)會被放到變量環(huán)境中畦娄,變量初始化為undefiend;在執(zhí)行階段弊仪,js引擎會從變量環(huán)境中查找自定義的變量和函數(shù)熙卡。

變量提升:js代碼執(zhí)行過程中,js引擎會把變量的聲明部分和函數(shù)的聲明部分提升到代碼開頭的“行為”励饵。變量被提升后會給變量設(shè)置默認(rèn)值undefined驳癌。

變量提升1.png

變量提升2.png

實(shí)際上變量和函數(shù)聲明在代碼里的位置是不會改變的,而是在編譯階段被js引擎放入內(nèi)存中役听。(js代碼先被js引擎編譯颓鲜,編譯完成后進(jìn)入執(zhí)行階段)。具體分析:
一典予、編譯階段
1甜滨、執(zhí)行上下文:js執(zhí)行一段代碼時的運(yùn)行環(huán)境,在執(zhí)行上下文中存在一個變量環(huán)境的對象(Viriable Environment)瘤袖,該對象中保存了變量提升的內(nèi)容衣摩。
示例:

showName()
console.log(myname)
var myname = '極客時間'
function showName() {
    console.log('函數(shù)showName被執(zhí)行');
}
  • 第 1 行和第 2 行,這兩行代碼不是聲明操作捂敌,所以 JavaScript 引擎不會做任何處理艾扮;
  • 第 3 行,由于這行是經(jīng)過 var 聲明的占婉,因此 JavaScript 引擎將在環(huán)境對象中創(chuàng)建一個名為 myname 的屬性泡嘴,并使用 undefined 對其初始化
  • 第 4 行逆济,JavaScript 引擎發(fā)現(xiàn)了一個通過 function 定義的函數(shù)酌予,所以它將函數(shù)定義存儲到堆 (HEAP)中,并在環(huán)境對象中創(chuàng)建一個 showName 的屬性奖慌,然后將該屬性值指向堆中函數(shù)的位置霎终。
    這樣就生成了變量環(huán)境對象。接下來 JavaScript 引擎會把聲明以外的代碼編譯為字節(jié)碼升薯。
    二、執(zhí)行階段
    JavaScript 引擎開始執(zhí)行“可執(zhí)行代碼”击困,按照順序一行一行地執(zhí)行涎劈。
js執(zhí)行流程圖.png
  • 注:如果存在同名的函數(shù)或者同名的變量广凸,在編譯階段,前者會被后者覆蓋蛛枚。即谅海,最終存儲在變量環(huán)境中的是最后定義的那個。如果變量和函數(shù)同名蹦浦,編譯階段扭吁,變量的聲明會被忽略,變量環(huán)境中存儲的是函數(shù)聲明盲镶,而不論順序(函數(shù)提升比變量提升優(yōu)先級高)

代碼編譯時有三種情況會創(chuàng)建執(zhí)行上下文

  • 當(dāng) JavaScript 執(zhí)行全局代碼的時候侥袜,會編譯全局代碼并創(chuàng)建全局執(zhí)行上下文,而且在整個頁面的生存周期內(nèi)溉贿,全局執(zhí)行上下文只有一份枫吧。
  • 當(dāng)調(diào)用一個函數(shù)的時候,函數(shù)體內(nèi)的代碼會被編譯宇色,并創(chuàng)建函數(shù)執(zhí)行上下文九杂,一般情況下,函數(shù)執(zhí)行結(jié)束之后宣蠕,創(chuàng)建的函數(shù)執(zhí)行上下文會被銷毀例隆。
  • 當(dāng)使用 eval 函數(shù)的時候,eval 的代碼也會被編譯抢蚀,并創(chuàng)建執(zhí)行上下文镀层。

調(diào)用棧:在執(zhí)行上下文創(chuàng)建好后,js引擎會將執(zhí)行上下文壓入棧中思币。這種用來管理執(zhí)行上下文的棧稱為執(zhí)行上下文棧鹿响,又稱調(diào)用棧。
示例:

var a = 2
function add(b,c) {
  return b+c
}
function addAll(b,c) {
var d = 10
result = add(b,c)
return  a+result+d
}
addAll(3,6)
  • 第一步谷饿,創(chuàng)建全局上下文惶我,并將其壓入棧低


    全局執(zhí)行上下文壓棧.png

    全局執(zhí)行上下文壓入到調(diào)用棧后,JavaScript 引擎便開始執(zhí)行全局代碼了博投。首先會執(zhí)行 a=2 的賦值操作绸贡,執(zhí)行該語句會將全局上下文變量環(huán)境中 a 的值設(shè)置為 2。

  • 第二步毅哗,調(diào)用addAll函數(shù)听怕。當(dāng)調(diào)用該函數(shù)時,js引擎會編譯該函數(shù)虑绵,并為其創(chuàng)建一個執(zhí)行上下文尿瞭,最后將該函數(shù)的執(zhí)行上下文壓入棧中


    執(zhí)行addAll時的調(diào)用棧.png

    addAll 函數(shù)的執(zhí)行上下文創(chuàng)建好之后,便進(jìn)入了函數(shù)代碼的執(zhí)行階段了翅睛,這里先執(zhí)行的是 d=10 的賦值操作声搁,執(zhí)行語句會將 addAll 函數(shù)執(zhí)行上下文中的 d 由 undefined 變成了 10黑竞。

  • 第三步,當(dāng)執(zhí)行到 add 函數(shù)調(diào)用語句時疏旨,同樣會為其創(chuàng)建執(zhí)行上下文很魂,并將其壓入調(diào)用棧


    執(zhí)行add時的調(diào)用棧.png
  • 第四步,當(dāng)add函數(shù)返回檐涝,該函數(shù)的執(zhí)行上下文會從棧頂彈出遏匆,并將result的值設(shè)置為add的返回值


    add執(zhí)行結(jié)束時的調(diào)用棧.png
  • addAll執(zhí)行完,其執(zhí)行上下文也會從棧頂彈出谁榜,此時只剩下全局上下文


    addAll執(zhí)行結(jié)束時的調(diào)用棧.png

    整個js流程執(zhí)行結(jié)束

二幅聘、js如何支持塊級作用域

作用域:全局作用域、函數(shù)作用域惰爬、塊級作用域
ES6通過let/const關(guān)鍵字可以實(shí)現(xiàn)塊級作用域喊暖,在編譯階段,js引擎不會把塊級作用域中的變量存放到變量環(huán)境中撕瞧,而是存放到詞法環(huán)境中陵叽,塊級作用域是通過詞法環(huán)境的棧結(jié)構(gòu)來實(shí)現(xiàn)的。
示例:

function foo(){
    var a = 1
    let b = 2
    {
      let b = 3
      var c = 4
      let d = 5
      console.log(a)
      console.log(b)
    }
    console.log(b) 
    console.log(c)
    console.log(d)
}   
foo()
  • 第一步:編譯并創(chuàng)建執(zhí)行上下文
    foo執(zhí)行上下文.png

    var聲明的變量在變量環(huán)境
    let/const聲明的變量在詞法環(huán)境
    在作用域塊內(nèi)部丛版,let/const聲明的變量不在詞法環(huán)境
  • 繼續(xù)執(zhí)行代碼巩掺,并賦值,當(dāng)進(jìn)入作用域塊中页畦,let/const聲明的變量會被存放在詞法環(huán)境的一個單獨(dú)區(qū)域中胖替。
    執(zhí)行到塊作用域時的上下文.png

    在詞法環(huán)境內(nèi)部,維護(hù)了一個小型棧結(jié)構(gòu)豫缨,棧底是函數(shù)最外層的變量独令,進(jìn)入一個作用域塊后,就會把該作用域塊內(nèi)部的變量壓到棧頂好芭;當(dāng)作用域執(zhí)行完成之后燃箭,該作用域的信息就會從棧頂彈出。
    塊級作用域就是通過詞法環(huán)境的棧結(jié)構(gòu)來實(shí)現(xiàn)的舍败,而變量提升是通過變量環(huán)境來實(shí)現(xiàn)招狸,通過這兩者的結(jié)合,JavaScript 引擎也就同時支持了變量提升和塊級作用域了邻薯。
    注:
  • var的創(chuàng)建和初始化被提升裙戏,賦值不會被提升
  • let/const的創(chuàng)建被提升,初始化和賦值不會被提升
  • function的創(chuàng)建、初始化和賦值均被提升

三厕诡、作用域鏈和閉包

每個執(zhí)行上下文的變量環(huán)境中都包含了一個外部引用outer累榜,用來指向外部的執(zhí)行上下文。
當(dāng)一段代碼使用一個變量時灵嫌,js引擎會先在“當(dāng)前的執(zhí)行上下文”中查找信柿,如果在當(dāng)前的變量環(huán)境中沒有找到冀偶,js引擎會繼續(xù)在outer所指向的執(zhí)行上下文中查找,這個查找的鏈條就稱為作用域鏈渔嚷。作用域是由代碼中函數(shù)聲明的位置來決定的

變量查找順序.png

閉包:在 JavaScript 中,內(nèi)部函數(shù)總是可以訪問其外部函數(shù)中聲明的變量稠曼,當(dāng)通過調(diào)用一個外部函數(shù)返回一個內(nèi)部函數(shù)后形病,即使該外部函數(shù)已經(jīng)執(zhí)行結(jié)束了,但是內(nèi)部函數(shù)引用外部函數(shù)的變量依然保存在內(nèi)存中霞幅,我們就把這些變量的集合稱為閉包漠吻。比如外部函數(shù)是 foo,那么這些變量的集合就稱為 foo 函數(shù)的閉包司恳。
閉包還可以這樣理解:當(dāng)函數(shù)嵌套時途乃,內(nèi)層函數(shù)引用了外層函數(shù)作用域下的變量,并且內(nèi)層函數(shù)在全局作用域下可訪問時扔傅,就形成了閉包耍共。

四、this

this和作用域鏈屬于兩套不同的系統(tǒng)猎塞。
執(zhí)行上下文中包含了變量環(huán)境试读、詞法環(huán)境、外部環(huán)境outer荠耽,還有一個this


執(zhí)行上下文.png

this是和上下文綁定的钩骇,每個執(zhí)行上下文都有一個this。
執(zhí)行上下文分文三種:全局執(zhí)行上下文铝量、函數(shù)執(zhí)行上下文和eval執(zhí)行上下文倘屹,所以this對應(yīng)的也有三種。

  1. 全局執(zhí)行上下文中的this:指向window對象
  2. 函數(shù)執(zhí)行上下文中的this:要取決于函數(shù)是如何調(diào)用的
    • 函數(shù)作為普通函數(shù)調(diào)用慢叨,在嚴(yán)格模式下纽匙,this 值是 undefined,非嚴(yán)格模式下 this 指向的是全局對象 window插爹;
    • 作為對象的方法調(diào)用哄辣,this指向該對象
    • 函數(shù)使用call、apply或bind方法調(diào)用赠尾,this由調(diào)用時傳入的參數(shù)決定力穗,指向傳入的參數(shù)(call、apply和bind都是用于指定函數(shù)中的this值的方法)
    • 在構(gòu)造函數(shù)中气嫁,this指向即將創(chuàng)建的新對象
    • 在事件處理函數(shù)中当窗,this指向觸發(fā)事件的元素。
    • 嵌套函數(shù)不會繼承外層的this寸宵,也是根據(jù)函數(shù)時如何調(diào)用來決定的崖面,但是箭頭函數(shù)不會創(chuàng)建其自身的執(zhí)行上下文元咙,箭頭函數(shù)中的this,指向外層非箭頭函數(shù)的this
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末巫员,一起剝皮案震驚了整個濱河市庶香,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌简识,老刑警劉巖赶掖,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異七扰,居然都是意外死亡奢赂,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進(jìn)店門颈走,熙熙樓的掌柜王于貴愁眉苦臉地迎上來膳灶,“玉大人,你說我怎么就攤上這事立由≡觯” “怎么了?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵拆吆,是天一觀的道長聋迎。 經(jīng)常有香客問我,道長枣耀,這世上最難降的妖魔是什么霉晕? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮捞奕,結(jié)果婚禮上牺堰,老公的妹妹穿的比我還像新娘。我一直安慰自己颅围,他們只是感情好伟葫,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著院促,像睡著了一般筏养。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上常拓,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天渐溶,我揣著相機(jī)與錄音,去河邊找鬼弄抬。 笑死茎辐,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播拖陆,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼弛槐,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了依啰?” 一聲冷哼從身側(cè)響起乎串,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎速警,沒想到半個月后灌闺,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡坏瞄,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了甩卓。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鸠匀。...
    茶點(diǎn)故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖逾柿,靈堂內(nèi)的尸體忽然破棺而出缀棍,到底是詐尸還是另有隱情,我是刑警寧澤机错,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布爬范,位于F島的核電站,受9級特大地震影響弱匪,放射性物質(zhì)發(fā)生泄漏青瀑。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一萧诫、第九天 我趴在偏房一處隱蔽的房頂上張望斥难。 院中可真熱鬧,春花似錦帘饶、人聲如沸哑诊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽镀裤。三九已至,卻和暖如春缴饭,著一層夾襖步出監(jiān)牢的瞬間暑劝,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工茴扁, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留铃岔,地道東北人。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像毁习,于是被迫代替她去往敵國和親智嚷。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評論 2 353

推薦閱讀更多精彩內(nèi)容