摘要:瀏覽器總是運行位于作用域鏈頂部的當(dāng)前執(zhí)行上下文。不同執(zhí)行上下文之間的變量命名沖突通過攀爬作用域鏈解決,從局部直到全局。它將攀爬作用域鏈檢查每一個執(zhí)行上下文的變量對象,尋找和變量名稱匹配的值。
1>什么是執(zhí)行上下文
Javascript中代碼的運行環(huán)境分為以下三種:
全局級別的代碼 - 這個是默認的代碼運行環(huán)境,一旦代碼被載入,引擎最先進入的就是這個環(huán)境。
函數(shù)級別的代碼 - 當(dāng)執(zhí)行一個函數(shù)時,運行函數(shù)體中的代碼。
Eval的代碼 - 在Eval函數(shù)內(nèi)運行的代碼。
javascript是一個單線程語言,這意味著在瀏覽器中同時只能做一件事情。當(dāng)javascript解釋器初始執(zhí)行代碼,它首先默認進入全局上下文。每次調(diào)用一個函數(shù)將會創(chuàng)建一個新的執(zhí)行上下文。
每次新創(chuàng)建的一個執(zhí)行上下文會被添加到作用域鏈的頂部,有時也稱為執(zhí)行或調(diào)用棧。瀏覽器總是運行位于作用域鏈頂部的當(dāng)前執(zhí)行上下文。一旦完成,當(dāng)前執(zhí)行上下文將從棧頂被移除并且將控制權(quán)歸還給之前的執(zhí)行上下文。
不同執(zhí)行上下文之間的變量命名沖突通過攀爬作用域鏈解決,從局部直到全局。這意味著具有相同名稱的局部變量在作用域鏈中有更高的優(yōu)先級。
簡單的說,每次你試圖訪問函數(shù)執(zhí)行上下文中的變量時,查找進程總是從自己的變量對象開始。如果在自己的變量對象中沒發(fā)現(xiàn)要查找的變量,繼續(xù)搜索作用域鏈。它將攀爬作用域鏈檢查每一個執(zhí)行上下文的變量對象,尋找和變量名稱匹配的值。
2>執(zhí)行上下文的建立過程
我們現(xiàn)在已經(jīng)知道,每當(dāng)調(diào)用一個函數(shù)時,一個新的執(zhí)行上下文就會被創(chuàng)建出來。然而,在javascript引擎內(nèi)部,這個上下文的創(chuàng)建過程具體分為兩個階段:
建立階段(發(fā)生在當(dāng)調(diào)用一個函數(shù)時,但是在執(zhí)行函數(shù)體內(nèi)的具體代碼以前)
建立變量,函數(shù),arguments對象,參數(shù)
建立作用域鏈
確定this的值
代碼執(zhí)行階段:
變量賦值,函數(shù)引用,執(zhí)行其它代碼
實際上,可以把執(zhí)行上下文看做一個對象,其下包含了以上3個屬性:
executionContextObj = { variableObject: { /* 函數(shù)中的arguments對象, 參數(shù),
內(nèi)部的變量以及函數(shù)聲明 / }, scopeChain: { / variableObject
以及所有父執(zhí)行上下文中的variableObject */ }, this: {} }
3>建立階段以及代碼執(zhí)行階段的詳細分析
確 切地說,執(zhí)行上下文對象(上述的executionContextObj)是在函數(shù)被調(diào)用時,但是在函數(shù)體被真正執(zhí)行以前所創(chuàng)建的。函數(shù)被調(diào)用時,就是我 上述所描述的兩個階段中的第一個階段 - 建立階段。這個時刻,引擎會檢查函數(shù)中的參數(shù),聲明的變量以及內(nèi)部函數(shù),然后基于這些信息建立執(zhí)行上下文對象 (executionContextObj)。在這個階段,variableObject對象,作用域鏈,以及this所指向的對象都會被確定。
上述第一個階段的具體過程如下:
1.找到當(dāng)前上下文中的調(diào)用函數(shù)的代碼 2.在執(zhí)行被調(diào)用的函數(shù)體中的代碼以前,開始創(chuàng)建執(zhí)行上下文 3.進入第一個階段-建立階段: 建立variableObject對象: 建立arguments對象,檢查當(dāng)前上下文中的參數(shù),建立該對象下的屬性以及屬性值 檢查當(dāng)前上下文中的函數(shù)聲明: 每找到一個函數(shù)聲明,就在variableObject下面用函數(shù)名建立一個屬性,屬性值就是指向該函數(shù)在內(nèi)存中的地址的一個引用 如果上述函數(shù)名已經(jīng)存在于variableObject下,那么對應(yīng)的屬性值會被新的引用所覆蓋。 檢查當(dāng)前上下文中的變量聲明: 每找到一個變量的聲明,就在variableObject下,用變量名建立一個屬性,屬性值為undefined。 如果該變量名已經(jīng)存在于variableObject屬性中,直接跳過(防止指向函數(shù)的屬性的值被變量屬性覆蓋為undefined),原屬性值不會被修改。 初始化作用域鏈 確定上下文中this的指向?qū)ο?4.代碼執(zhí)行階段: 執(zhí)行函數(shù)體中的代碼,一行一行地運行代碼,給variableObject中的變量屬性賦值。 下面來看個具體的代碼示例: function foo(i) { var a = "hello"; var b = function privateB() { }; function c() { } } foo(22); 在調(diào)用foo(22)的時候,建立階段如下: fooExecutionContext = { variableObject: { arguments: { 0: 22, length: 1 }, i: 22, c: pointer to function c() a: undefined, b: undefined }, scopeChain: { ... }, this: { ... } } 由此可見,在建立階段,除了arguments,函數(shù)的聲明,以及參數(shù)被賦予了具體的屬性值,其它的變量屬性默認的都是undefined。一旦上述建立階段結(jié)束,引擎就會進入代碼執(zhí)行階段,這個階段完成后,上述執(zhí)行上下文對象如下: fooExecutionContext = { variableObject: { arguments: { 0: 22, length: 1 }, i: 22, c: pointer to function c() a: "hello", b: pointer to function privateB() }, scopeChain: { ... }, this: { ... } } 我們看到,只有在代碼執(zhí)行階段,變量屬性才會被賦予具體的值。
4>局部變量作用域提升的緣由
在網(wǎng)上一直看到這樣的總結(jié): 在函數(shù)中聲明的變量以及函數(shù),其作用域提升到函數(shù)頂部,換句話說,就是一進入函數(shù)體,就可以訪問到其中聲明的變量以及函數(shù)。這是對的,但是知道其中的緣由嗎?相信你通過上述的解釋應(yīng)該也有所明白了。不過在這邊再分析一下??聪旅嬉欢未a:
function() { console.log(typeof foo); // function pointer console.log(typeof bar); // undefined var foo = "hello", bar = function() { return "world"; }; function foo() { return "hello"; } }()); 上述代碼定義了一個匿名函數(shù),并且通過()運算符強制理解執(zhí)行。那么我們知道這個時候就會有個執(zhí)行上下文被創(chuàng)建,我們看到例子中馬上可以訪問foo以及bar變量,并且通過typeof輸出foo為一個函數(shù)引用,bar為undefined。 為什么我們可以在聲明foo變量以前就可以訪問到foo呢?
因 為在上下文的建立階段,先是處理arguments, 參數(shù),接著是函數(shù)的聲明,最后是變量的聲明。那么,發(fā)現(xiàn)foo函數(shù)的聲明后,就會在variableObject下面建立一個foo屬性,其值是一個指向 函數(shù)的引用。當(dāng)處理變量聲明的時候,發(fā)現(xiàn)有var foo的聲明,但是variableObject已經(jīng)具有了foo屬性,所以直接跳過。當(dāng)進入代碼執(zhí)行階段的時候,就可以通過訪問到foo屬性了,因為它 已經(jīng)就存在,并且是一個函數(shù)引用。
為什么bar是undefined呢?
因為bar是變量的聲明,在建立階段的時候,被賦予的默認的值為undefined。由于它只要在代碼執(zhí)行階段才會被賦予具體的值,所以,當(dāng)調(diào)用typeof(bar)的時候輸出的值為undefined。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.hztianpu.com/yun/83649.html
摘要:當(dāng)被創(chuàng)建時,它的作用域鏈初始化為當(dāng)前運行函數(shù)的屬性中的對象,這些值按照他們出現(xiàn)在函數(shù)中的順序,被復(fù)制到執(zhí)行環(huán)境的作用域鏈中。然后這個對象被推入作用域鏈最前端。 在計算機科學(xué)中,數(shù)據(jù)存儲的位置關(guān)系到代碼執(zhí)行過程中數(shù)據(jù)的檢索速度,有一個經(jīng)典的問題即為:通過改變數(shù)據(jù)的存儲位置來獲得最佳的讀寫性能。 Javascript中四種基本的數(shù)據(jù)存儲位置 字面量字面量只代表自身,不存儲在特定的位置。...
摘要:為什么會這樣這段代碼究竟是如何運行的執(zhí)行上下文堆棧瀏覽器中的解釋器單線程運行。瀏覽器始終執(zhí)行位于堆棧頂部的,并且一旦函數(shù)完成執(zhí)行當(dāng)前操作,它將從堆棧頂部彈出,將控制權(quán)返回到當(dāng)前堆棧中的下方上下文。確定在上下文中的值。 原文:What is the Execution Context & Stack in JavaScript? git地址:JavaScript中的執(zhí)行上下文和隊列(...
摘要:當(dāng)遇到函數(shù)調(diào)用時,引擎為該函數(shù)創(chuàng)建一個新的執(zhí)行上下文并把它壓入當(dāng)前執(zhí)行棧的頂部。參考鏈接理解中的執(zhí)行上下文和執(zhí)行棧深入之執(zhí)行上下文棧 開篇 作為一個JavaScript的程序開發(fā)者,如果被問到JavaScript代碼的執(zhí)行順序,你腦海中是不是有一個直觀的印象 -- JavaScript 是順序執(zhí)行的,可事實真的是這樣的嗎? 讓我們首先看兩個小例子: var foo = functio...
摘要:講作用域鏈?zhǔn)紫纫獜淖饔糜蛑v起,下面是百度百科里對作用域的定義作用域在許多程序設(shè)計語言中非常重要。原文出處談?wù)務(wù)Z法里一些難點問題二 3) 作用域鏈相關(guān)的問題 作用域鏈?zhǔn)莏avascript語言里非常紅的概念,很多學(xué)習(xí)和使用javascript語言的程序員都知道作用域鏈?zhǔn)抢斫鈐avascript里很重要的一些概念的關(guān)鍵,這些概念包括this指針,閉包等等,它非常紅的另一個重要原因就...
閱讀 3053·2021-11-24 09:39
閱讀 3693·2021-11-22 13:54
閱讀 3475·2021-11-16 11:45
閱讀 2549·2021-09-09 09:33
閱讀 3273·2019-08-30 15:55
閱讀 1355·2019-08-29 15:40
閱讀 991·2019-08-29 15:19
閱讀 3483·2019-08-29 15:14