在JavaScript中,確定不可避免的須要聲明變量和函數,JS編譯器是如何找到這些變量的呢?javascript
咱們還得對執行上下文有一個進一步的瞭解。前端
在上一篇文章中已經知道,當調用一個函數時(激活),一個新的執行上下文就會被建立。一個執行上下文的生命週期能夠分爲兩個階段。java
•建立階段面試
在這個階段中,執行上下文會分別建立變量對象,創建做用域鏈,以及肯定this指向。瀏覽器
•代碼執行階段微信
建立完成以後,就會開始執行代碼,會完成變量賦值,函數引用,以及執行其餘代碼。前端工程師
從這裏能夠看出詳細瞭解執行上下文極爲重要,由於其中涉及到了變量對象,做用域鏈,this等不少人沒有怎麼弄明白,可是卻極爲重要的概念,它關係到咱們能不能真正理解JavaScript。在後面的文章中咱們會一一詳細總結,本文的核心是變量對象。app
變量對象(Variable Object)
變量對象的建立,依次經歷瞭如下幾個過程。異步
// 這裏a爲屬性名,20是屬性值{ a: 20}
1、創建arguments對象:檢查當前上下文中的參數,創建該對象下的屬性與 屬性值。async
函數參數
2、檢查當前上下文的函數聲明,也就是使用function關鍵字聲明的函數。在變量對象中以函數名創建一個屬性,屬性值爲指向該函數所在內存地址的引用
3、檢查當前上下文中的變量聲明,每找到一個變量聲明,就在變量對象中以變量名創建一個屬性,屬性值爲undefined
若是變量與函數同名,則在這個階段,以函數值爲準
console.log(foo); // function foofunction foo() { console.log('function foo') }var foo = 20;
// 上慄的執行順序爲
// 首先將全部函數聲明放入變量對象中function foo() { console.log('function foo') }
// 其次將全部變量聲明放入變量對象中,可是由於foo已經存在同名函數,此時以函數值爲準,而不會被undefined覆蓋// var foo = undefined;
// 而後開始執行階段代碼的執行console.log(foo); // function foofoo = 20;
根據這個規則,理解變量提高就變得十分簡單了。在不少文章中雖然提到了變量提高,可是具體是怎麼回事還真的不少人都說不出來,之後在面試中用變量對象的建立過程跟面試官解釋變量提高,簡直逼格滿滿。
在上面的規則中咱們看出,function聲明會比var聲明優先級更高一點。爲了幫助你們更好的理解變量對象,咱們結合一些簡單的例子來進行探討。
// demo01function test() { console.log(a); console.log(foo());
var a = 1; function foo() { return 2; }}
test();
在上例中,咱們直接從test()的執行上下文開始理解。全局做用域中運行test()
時,test()的執行上下文開始建立。爲了便於理解,咱們用以下的形式來表示
// 建立過程testEC = { // 變量對象 VO: {}, scopeChain: {}}
// 由於本文暫時不詳細解釋做用域鏈,因此把變量對象專門提出來講明
// VO 爲 Variable Object的縮寫,即變量對象VO = { arguments: {...}, //注:在瀏覽器的展現中,函數的參數可能並非放在arguments對象中,這裏爲了方便理解,我作了這樣的處理 foo: <foo reference> // 表示foo的地址引用 a: undefined}
未進入執行階段以前,變量對象中的屬性都不能訪問!可是進入執行階段以後,變量對象轉變爲了活動對象,裏面的屬性都能被訪問了,而後開始進行執行階段的操做。
這樣,若是面試的時候被問到變量對象和活動對象有什麼區別,就能夠自如的應答了,他們其實都是同一個對象,只是處於執行上下文的不一樣生命週期。不過只有處於函數調用棧棧頂的執行上下文中的變量對象,纔會變成活動對象。
// 執行階段VO -> AO // Active ObjectAO = { arguments: {...}, foo: <foo reference>, a: 1, this: Window}
所以,上面的例子demo1,執行順序就變成了這樣
function test() { function foo() { return 2; } var a; console.log(a); console.log(foo()); a = 1;}
test();
再來一個例子,鞏固一下咱們的理解。
// demo2function test() { console.log(foo); console.log(bar);
var foo = 'Hello'; console.log(foo); var bar = function () { return 'world'; }
function foo() { return 'hello'; }}
test();
// 建立階段VO = { arguments: {...}, foo: <foo reference>, bar: undefined}// 這裏有一個須要注意的地方,var聲明的變量與函數同名,以函數爲準
// 執行階段VO -> AOVO = { arguments: {...}, foo: 'Hello', bar: <bar reference>, this: Window}
須要結合上面的知識,仔細對比這個例子中變量對象從建立階段到執行階段的變化,若是你已經理解了,說明變量對象相關的東西都已經難不倒你了。
全局上下文的變量對象
以瀏覽器中爲例,全局對象爲window。全局上下文有一個特殊的地方,它的變量對象,就是window對象。而這個特殊,在this指向上也一樣適用,this也是指向window。
// 以瀏覽器中爲例,全局對象爲window// 全局上下文windowEC = { VO: Window, scopeChain: {}, this: Window}
除此以外,全局上下文的生命週期,與程序的生命週期一致,只要程序運行不結束,好比關掉瀏覽器窗口,全局上下文就會一直存在。其餘全部的上下文環境,都能直接訪問全局上下文的屬性。
let/const
ES6中,新增了使用let/const來聲明變量。我想他們的使用確定難不倒你們。但是有一個問題不知道你們思考過沒有,let/const聲明的變量,是否還會變量提高?
是的,這個刁鑽的問題也成爲了各大面試官愛問的細節。很賤!可也沒辦法,仍是要弄明白怎麼回事!
咱們來作個試驗,驗證一下這個問題:
第一步,咱們直接使用一個未定義的變量
console.log(a);
報錯信息以下:
第二步,咱們在let以前調用變量
console.log(a);let a = 10;
會發生什麼?會打印出undefined嗎?
看看結果
不能在初始化以前訪問a。
這個報錯說明了什麼問題呢?變量定義了,可是沒有初始化。
因此在這裏咱們就能夠得出結論:let/const聲明的變量,仍然會提早被收集到變量對象中,但和var不一樣的是,let/const定義的變量,不會在這個時候給他賦值undefined。
由於徹底沒有賦值,即便變量提高了,咱們也不能在賦值以前調用他。這就是咱們常說的暫時性死區。
最後,變量提高的現象確實會對咱們的代碼形成一些負面影響,所以,開發中的好習慣,就是儘可能將變量聲明放在最前面來寫。
❤️愛心三連擊
1.看到這裏了就點個在看支持下吧,你的「在看」是我創做的動力。
2.關注公衆號圖雀社區
,「帶你一塊兒學優質實戰技術教程」!
3.特殊階段,帶好口罩,作好我的防禦。
4.添加微信【little-tuture】,拉你進技術交流羣一塊兒學習。
·END·
匯聚精彩的免費實戰教程
喜歡本文,點個「在看」告訴我
![](http://static.javashuo.com/static/loading.gif)
本文分享自微信公衆號 - 圖雀社區(tuture-dev)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。