function f() { console.log(a); // ? var a = 1; } f();
簡單簡單,打印結果是 undefined。爲啥?由於變量提高,變量 a 的聲明被提高到當前做用域的頂部了。也就是能夠想象成這樣:函數
function f() { var a; console.log(a); a = 1; }
此外,還有函數提高,和變量提高相似:this
f1(); f2(); function f1() { console.log(1) } function f2() { console.log(2) }
f1 和 f2 被提高了,因此不會報錯。不少文章中都會常常提到提高這個概念。
可是,這是真的嗎?JS 解釋器在執行代碼時還順帶幫你變更一下代碼的順序?想一想也以爲不太可能嘛~那麼究竟是咋回事捏?code
JavaScript 的可執行代碼(executable code)有三種,分別爲 全局代碼
、函數代碼
以及 eval 代碼
。鑑於 eval
不被推薦使用,咱就不理它。對象
當執行全局代碼時,會建立一個全局執行上下文。當執行一個函數的時候,會建立一個函數執行上下文。全局執行上下文只會有一個,而函數執行上下文能夠有不少不少個。JS 引擎會建立 執行上下文棧(execution context stack, ECS)
去管理這些執行上下文。咱們以函數執行上下文爲例。ip
對於一個執行上下文而言,能夠抽象化爲這樣一個對象:作用域
contextObject = { VariableObject, ScopeChain, thisValue }
其中變量對象(Variable Object,VO)是包含與當前執行上下文相關的數據做用域,它存儲着當前上下文中定義的變量聲明、函數聲明(注意:不包含函數表達式),另外函數執行上下文中還會有參數。io
變量對象是一個抽象概念,在不一樣的執行上下文中會以不一樣的對象呈現。好比在全局上下文中,變量對象就是全局對象自己(這也是爲何咱們可以經過全局對象的屬性名稱引用全局變量)。而對於函數執行上下文,是用活動對象(Activation Object,AO)來表示變量對象的,咱們訪問的即是活動對象上的屬性。console
執行上下文中的代碼會被分出兩個階段進行處理,分別爲:table
此時進入執行上下文,但在開始執行代碼以前。此時的變量對象:function
瞭解了第一個階段,那麼來看個例子:
function f(a, b) { var c = 3; function d() {}; var e = function() {}; (function f() {}); } f(1, 2);
當執行 f 函數時,進入執行上下文,咱們能夠描述出此時的 AO:
AO = { arguments: { 0: 1, 1: 2, length: 2 }, a: 1, b: 2, c: undefined, d: reference to function() {}, e: undefined, } // 注意:(function f() {}) 爲函數表達式,不會被處理
執行階段就相對簡單,代碼順序執行,而且會根據代碼去修改變量對象中的值,因此當函數執行完後的 AO 會是:
AO = { arguments: { 0: 1, 1: 2, length: 2 }, a: 1, b: 2, c: 3, d: reference to function() {}, e: reference to function() {}, }
綜上所述:
console.log(f); // ? function f() {} var f = 1;
打印結果會是一個函數對象。由於第一個階段中先處理函數聲明後處理變量聲明,當處理變量聲明時變量對象中已經存在同名屬性 f,不會作任何事直接跳過,因此 f 的值會是一個函數。
你也能夠多找幾道題嘗試分析以加深理解。固然,平常工做中仍是直接想成提高來得方便些,只是但願經過本文能讓你瞭解到一些背後的事情。最後,若是文中有任何錯誤或不足之處,還請指出~