摘要:最先執(zhí)行完畢的一定是最里面的函數(shù),執(zhí)行過后彈出調(diào)用棧,接著執(zhí)行上一層函數(shù),直至所有函數(shù)執(zhí)行完,調(diào)用棧清空。到這里你應(yīng)該就會(huì)明白,上面函數(shù)調(diào)用棧,就是生成了一個(gè)函數(shù)的執(zhí)行上下文。
前言 為什么會(huì)有這篇文章?
在書籍或博客上,我們經(jīng)常會(huì)看到「作用域鏈」、「閉包」、「變量提升」等概念,說明一個(gè)問題 —— 它們很重要。
但很多時(shí)候,對(duì)于這些概念,看的時(shí)候覺得自己已經(jīng)明白了,可過不了多久,再讓你說一說,可能就說不清楚了,之所以會(huì)這樣,是因?yàn)槲覀儗?duì)于 JavaScript 這門語言的運(yùn)行機(jī)制不清楚。
我相信搞明白了今天所講的內(nèi)容,會(huì)對(duì)你理解那些知識(shí)大有裨益!
函數(shù)調(diào)用棧(call stack) 1. 什么是棧?類似 js 中的數(shù)組,棧也是用來存儲(chǔ)數(shù)據(jù)的一種數(shù)據(jù)結(jié)構(gòu)。他的特點(diǎn)是后進(jìn)先出(LIFO)。
與之相對(duì)的一種數(shù)據(jù)結(jié)構(gòu)稱為隊(duì)列,隊(duì)列的特點(diǎn)是先進(jìn)先出(FIFO)。
可以想象這樣一種場(chǎng)景:小明和同學(xué)們放學(xué)回家,老師讓他在排在隊(duì)伍的最前面,他們每天回家路上都要經(jīng)過一個(gè)胡同,小明每次都是第一個(gè)進(jìn)入胡同,肯定也是第一個(gè)出來,這就是所謂「先進(jìn)先出」。
可是有一天,小明他們走到胡同里發(fā)現(xiàn)胡同口停了一輛車,把胡同給堵死了,沒辦法,他們只能隊(duì)頭變隊(duì)尾往回撤,這時(shí)候,小明雖然最先進(jìn)入胡同,卻只能最后出去,最先出去的是排在隊(duì)尾的小華,也就是「后進(jìn)先出」。
2. 什么叫函數(shù)調(diào)用棧?在 js 中函數(shù)的調(diào)用也遵照這樣以一個(gè)原則:最先調(diào)用的函數(shù)先放到調(diào)用棧中,假如這個(gè)函數(shù)內(nèi)部又調(diào)用了別的函數(shù),那么這個(gè)內(nèi)部函數(shù)就接著被放入調(diào)用棧中,直至不再有函數(shù)調(diào)用。最先執(zhí)行完畢的一定是最里面的函數(shù),執(zhí)行過后彈出調(diào)用棧,接著執(zhí)行上一層函數(shù),直至所有函數(shù)執(zhí)行完,調(diào)用棧清空。
這樣說可能會(huì)不太明白,舉個(gè)例子:
// 其他語句 function first() { console.log("first") function second() { console.log("second") } second(); third(); // 其他語句 } //其他語句 function third() { console.log("third") } // 調(diào)用 first first();
在上述代碼中,首先調(diào)用的是函數(shù) first, 此時(shí) first 進(jìn)入函數(shù)棧,接著在 first 中調(diào)用函數(shù) second,second 入棧,當(dāng) second 執(zhí)行完畢后,second 出棧,third 入棧,接著 third 執(zhí)行完出棧,執(zhí)行 first 其他代碼,直至 first 執(zhí)行完,函數(shù)棧清空。
執(zhí)行上下文(Execution Context) 1. 什么是執(zhí)行上下文?js 代碼在執(zhí)行時(shí),會(huì)進(jìn)入一個(gè)執(zhí)行環(huán)境,它會(huì)形成一個(gè)作用域。這個(gè)執(zhí)行環(huán)境,便是執(zhí)行上下文。
JavaScript 主要有三種執(zhí)行環(huán)境:
全局執(zhí)行環(huán)境: 代碼開始執(zhí)行時(shí)首先進(jìn)入的環(huán)境。
函數(shù)環(huán)境:函數(shù)調(diào)用時(shí),會(huì)開始執(zhí)行函數(shù)中的代碼。
eval:不建議使用,可忽略。
2. 執(zhí)行上下文的生命周期上面講到 js 代碼執(zhí)行時(shí)會(huì)生成一個(gè)執(zhí)行上下文。而這個(gè)執(zhí)行上下文的周期,分為兩個(gè)階段:
創(chuàng)建階段。這個(gè)階段會(huì)生成變量對(duì)象(VO),建立作用域鏈以及確定 this 的值。
執(zhí)行階段。這個(gè)階段進(jìn)行變量賦值,函數(shù)引用及執(zhí)行代碼。
到這里你應(yīng)該就會(huì)明白,上面函數(shù)調(diào)用棧,就是生成了一個(gè)函數(shù)的執(zhí)行上下文。
執(zhí)行上下文也同樣遵循函數(shù)調(diào)用棧的規(guī)則,無非就是多加了一層 —— 全局執(zhí)行上下文,函數(shù)執(zhí)行完后會(huì)跳出執(zhí)行棧,而全局執(zhí)行上下文,會(huì)在關(guān)閉瀏覽器后跳出執(zhí)行棧。
還是上面的例子,我們看一下執(zhí)行棧。
變量對(duì)象 1. 什么叫變量對(duì)象?從上面其實(shí)可以得到答案,變量對(duì)象是 js 代碼在進(jìn)入執(zhí)行上下文時(shí),js 引擎在內(nèi)存中建立的一個(gè)對(duì)象,用來存放當(dāng)前執(zhí)行環(huán)境中的變量。
2. 變量對(duì)象(VO)的創(chuàng)建過程變量對(duì)象的創(chuàng)建,是在執(zhí)行上下文創(chuàng)建階段,依次經(jīng)過以下三個(gè)過程:
創(chuàng)建 arguments 對(duì)象。對(duì)于函數(shù)執(zhí)行環(huán)境,首先查詢是否有傳入的實(shí)參,如果有,則會(huì)將參數(shù)名是實(shí)參值組成的鍵值對(duì)放入arguments 對(duì)象中,否則,將參數(shù)名和 undefined,組成的鍵值對(duì)放入 arguments 對(duì)象中。
function bar(a, b, c) { console.log(arguments); // [2, 4] console.log(arguments[2]); // undefined } bar(2,4)
檢查當(dāng)前環(huán)境中的函數(shù)聲明。當(dāng)遇到同名的函數(shù)時(shí),后面的會(huì)覆蓋前面的。
console.log(a); // function a() {console.log("fjdsfs") } function a() { console.log("24"); } function a() { console.log("fjdsfs") }
在上面的例子中,在執(zhí)行第一行代碼之前,函數(shù)聲明已經(jīng)創(chuàng)建完成,后面的對(duì)之前的聲明進(jìn)行了覆蓋。
檢查當(dāng)前環(huán)境中的變量聲明并賦值為undefined。當(dāng)遇到同名的函數(shù)聲明,為了避免函數(shù)被賦值為 undefined ,會(huì)忽略此聲明
console.log(a); // function a() {console.log("fjdsfs") } console.log(b); // undefined function a() { console.log("24"); } function a() { console.log("fjdsfs"); } var b = "bbbbbbbb"; var a = 46;
在上例我們可以看到,在代碼之前前,a 仍舊是一個(gè)函數(shù),而 b 是 undefined。
根據(jù)以上三個(gè)步驟,對(duì)于變量提升也就知道是怎么回事了。
3. 變量對(duì)象變?yōu)榛顒?dòng)對(duì)象執(zhí)行上下文的第二個(gè)階段,稱為執(zhí)行階段,在此時(shí),會(huì)進(jìn)行變量賦值,函數(shù)引用并執(zhí)行其他代碼,此時(shí),變量對(duì)象變?yōu)?strong>活動(dòng)對(duì)象。
我們還是舉上面的例子:
console.log(a); // function a() {console.log("fjdsfs") } console.log(b); // undefined function a() { console.log("24"); } function a() { console.log("fjdsfs"); } var b = "bbbb"; console.log(b); // "bbbb" var a = 46; console.log(a); // 46 var b = "hahahah"; console.log(b); // "hahah"
在上面的代碼中,代碼真正開始執(zhí)行是從第一行 console.log() 開始的,自這之前,執(zhí)行上下文是這樣的:
// 創(chuàng)建過程 EC= { VO: {}; // 創(chuàng)建變量對(duì)象 scopeChain: {}; // 作用域鏈 } VO = { argument: {...}; // 當(dāng)前為全局上下文,所以這個(gè)屬性值是空的 a: // 函數(shù) a 的引用地址 b: undefiend // 見上文創(chuàng)建變量對(duì)象的第三步 }
根據(jù)步驟,首先是 arguments 對(duì)象的創(chuàng)建;其次,是檢查函數(shù)的聲明,此時(shí),函數(shù) a 聲明了兩次,后一次將覆蓋前一次;最后,是檢查變量的聲明,先聲明了變量 b,將它賦值為 undefined,接著遇到 a 的聲明,由于 a 已經(jīng)聲明為了一個(gè)函數(shù),所以,此條聲明將會(huì)被忽略。
到此,變量對(duì)象的創(chuàng)建階段完成,接下來時(shí)執(zhí)行階段,我們一步一步來。
執(zhí)行 console.log(a),我們知道,此時(shí) a 是第二個(gè)函數(shù),所以會(huì)輸出function a() {...};
執(zhí)行 console.log(b),不出我們所料,將會(huì)輸出 undefined;
執(zhí)行賦值操作: b = "bbbb";
執(zhí)行 console.log(b) ,此時(shí),b 已經(jīng)賦值,所以會(huì)輸出 "bbbb";
執(zhí)行賦值操作: a = 46;
執(zhí)行 console.log(a) ,此時(shí),a 的值變?yōu)?46。
執(zhí)行賦值操作: b = "hahahah";
執(zhí)行 console.log(b), b 已經(jīng)被重新賦值,輸出 hahahah。
由上面我們可以看到,在執(zhí)行階段,變量對(duì)象是跟著代碼不斷變化的,此時(shí),我們把變量對(duì)象成為活動(dòng)對(duì)象。
執(zhí)行到最后一步時(shí),執(zhí)行上下文變成了這樣。
// 執(zhí)行階段 EC = { VO = {}; scopeChain: {}; } // VO ---- AO AO = { argument: {...}; a: 46; b: "hahahah"; this: window; }
以上,就是變量對(duì)象在代碼執(zhí)行前及執(zhí)行時(shí)的變化。
剛開始就說過,這部分概念將會(huì)對(duì)你理解后面的知識(shí)有很大的幫助,所以剛開始接觸的話可能會(huì)有些晦澀,建議就是認(rèn)真讀兩遍,結(jié)合后面的知識(shí),經(jīng)常回過頭來看看。
最后留一道題,給大家作為練手,觀察觀察執(zhí)行上下文及變量對(duì)象的變化。
console.log(a); console.log(b); var a = 4; function a() { console.log("我是a1"); b(3, 5); } var a = function a() { console.log("我是a2"); b(3, 5); } var b = function (m, n) { console.log(arguments); console.log("b") } a();
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/99383.html
摘要:最先執(zhí)行完畢的一定是最里面的函數(shù),執(zhí)行過后彈出調(diào)用棧,接著執(zhí)行上一層函數(shù),直至所有函數(shù)執(zhí)行完,調(diào)用棧清空。到這里你應(yīng)該就會(huì)明白,上面函數(shù)調(diào)用棧,就是生成了一個(gè)函數(shù)的執(zhí)行上下文。 showImg(http://upload-images.jianshu.io/upload_images/7803415-36e8e7d048f63524.jpg?imageMogr2/auto-orient...
摘要:最先執(zhí)行完畢的一定是最里面的函數(shù),執(zhí)行過后彈出調(diào)用棧,接著執(zhí)行上一層函數(shù),直至所有函數(shù)執(zhí)行完,調(diào)用棧清空。到這里你應(yīng)該就會(huì)明白,上面函數(shù)調(diào)用棧,就是生成了一個(gè)函數(shù)的執(zhí)行上下文。 showImg(http://upload-images.jianshu.io/upload_images/7803415-36e8e7d048f63524.jpg?imageMogr2/auto-orient...
摘要:以上簡單總結(jié)了下對(duì)執(zhí)行上下文和變量對(duì)象的理解,主要在于記錄總結(jié)一下學(xué)習(xí)成果,目前文章的水平實(shí)在不敢談分享。 執(zhí)行上下文(Execution Context) 文章同步到github javaScript中的執(zhí)行上下文和變量對(duì)象 JavaScript代碼執(zhí)行的過程,包括編譯和執(zhí)行兩個(gè)階段,編譯就是通過詞法分析,構(gòu)建抽象抽象語法樹,并編譯成機(jī)器識(shí)別的指令,在JavaScript代碼編譯階段...
摘要:在中,通過棧的存取方式來管理執(zhí)行上下文,我們可稱其為執(zhí)行棧,或函數(shù)調(diào)用棧。而處于棧頂?shù)氖钱?dāng)前正在執(zhí)行函數(shù)的執(zhí)行上下文,當(dāng)函數(shù)調(diào)用完成后,它就會(huì)從棧頂被推出理想的情況下,閉包會(huì)阻止該操作,閉包后續(xù)文章深入詳解。 寫在開篇 已經(jīng)不敢自稱前端小白,曾經(jīng)吹過的牛逼總要一點(diǎn)點(diǎn)去實(shí)現(xiàn)。 正如前領(lǐng)導(dǎo)說的,自己喝酒吹過的牛皮,跪著都得含著淚去實(shí)現(xiàn)。 那么沒有年終完美總結(jié),來個(gè)新年莽撞開始可好。 進(jìn)擊巨...
摘要:在中,通過棧的存取方式來管理執(zhí)行上下文,我們可稱其為執(zhí)行棧,或函數(shù)調(diào)用棧。因?yàn)閳?zhí)行中最先進(jìn)入全局環(huán)境,所以處于棧底的永遠(yuǎn)是全局環(huán)境的執(zhí)行上下文。 一、什么是執(zhí)行上下文? 執(zhí)行上下文(Execution Context): 函數(shù)執(zhí)行前進(jìn)行的準(zhǔn)備工作(也稱執(zhí)行上下文環(huán)境) JavaScript在執(zhí)行一個(gè)代碼段之前,即解析(預(yù)處理)階段,會(huì)先進(jìn)行一些準(zhǔn)備工作,例如掃描JS中var定義的變量、...
閱讀 1275·2019-08-30 12:49
閱讀 3120·2019-08-28 18:14
閱讀 823·2019-08-26 11:38
閱讀 1681·2019-08-23 18:23
閱讀 2825·2019-08-23 17:04
閱讀 504·2019-08-23 16:52
閱讀 4025·2019-08-23 16:43
閱讀 2774·2019-08-23 16:12