《javascript高級程序設計》筆記:變量對象與預解析

上一篇:《javascript高級程序設計》筆記:內存與執行環境
clipboard.pngjavascript

上篇文章中說到:
(1)當執行流進入函數時,對應的執行環境就會生成
(2)執行環境建立時會生成變量對象,肯定做用域鏈,肯定this指向
(2)每一個執行環境都有一個與之關聯的變量對象,環境中定義的全部變量和函數都保存在這個對象中java

1. 變量對象

變量對象是在進入執行環境就肯定下來的,顧名思義,變量對象是用於存儲在這個執行環境中的全部變量和函數的一個對象。只是這個對象是用於解析器處理數據時使用,咱們沒法直接調用segmentfault

下圖描述了執行流在執行環境中的執行過程(執行環境的生命週期)函數

clipboard.png

(1)創建arguments對象。檢查當前上下文中的參數,創建該對象下的屬性與屬性值。this

(2)檢查當前上下文的函數聲明,也就是使用function關鍵字聲明的函數。在變量對象中以函數名創建一個屬性,屬性值爲指向該函數所在內存地址的引用。若是函數名的屬性已經存在,那麼該屬性將會被新的引用所覆蓋。spa

(3)檢查當前上下文中的變量聲明,每找到一個變量聲明,就在變量對象中以變量名創建一個屬性,屬性值爲undefined。若是該變量名的屬性已經存在,爲了防止同名的函數被修改成undefined,則會直接跳過,原屬性值不會被修改。設計

總之:function聲明會比var聲明優先級更高一點code

下面經過具體的例子來看變量對象:對象

function test() {
  console.log(a);
  console.log(foo());

  var a = 1;
  function foo() {
    return 2;
  }
}

test();

當執行到test()時會生成執行環境testEC,具體形式以下:blog

// 建立過程
testEC = {
  // 變量對象(variable object)
  VO: {},
  // 做用域鏈
  scopeChain: [],
  // this指向
  this: {}
}

僅針對變量對象來具體展開:

VO = {
  // 傳參對象
  arguments: {},
  // 在testEC中定義的function
  foo: "<foo reference>",
  // 在testEC中定義的var
  a: undefined
}

未進入執行階段以前,變量對象中的屬性都不能訪問!可是進入執行階段以後,變量對象轉變爲了活動對象,裏面的屬性都能被訪問了,而後開始進行執行階段的操做

// 執行階段
VO ->  AO   // Active Object
AO = {
  arguments: {...},
  foo: function(){return 2},
  a: 1
}

最後咱們將變量對象建立時的VO和執行階段的AO整合到一塊兒就能夠獲得整個執行環境中代碼的執行順序:

function test() {
  function foo() {
    return 2;
  }
  var a;
  console.log(a);// undefined
  console.log(foo());// 2
  a = 1;
}

test();

這個就是分步變量對象的建立和執行階段來解讀預解析

2. 預解析

預解析分爲變量提高和函數提高
變量提高:提高的是當前變量的聲明,賦值還保留在原來的位置
函數提高:函數聲明,能夠認爲是把整個函數體聲明瞭
函數表達式方式定義函數至關於變量聲明 var fn = function(){}

一個簡單的變量提高的例子

!function(){
  console.log(a);
  var a = 1;
}()

// 實際執行順序
!function(){
  var a;
  console.log(a);
  a = 1;
}()

技巧:
將執行過程手動拆分紅兩個步驟
1.建立階段:也稱做編譯階段,主要是變量的聲明和函數的定義(找var和function)
2.執行階段:變量賦值和函數執行

這樣看預解析仍是比較容易的,可是涉及到命名衝突時,又是怎樣的狀況呢?

(1)一個函數和一個變量出現同名
狀況一:變量只聲明瞭,但沒有賦值

!function(){
  var f;
  function f() {};
  console.log(f); // f(){}
}()

狀況二:變量只聲明且賦值

!function(){
  console.log(f); // f(){}
  var f = 123;
  function f() {};
  console.log(f); // 123
}()

結論
1.一個函數和一個變量出現同名,若是是變量只聲明瞭,但沒有賦值,變量名會被忽略
2.一個函數和一個變量出現同名,變量聲明且賦值,賦值前值爲函數,賦值後爲變量的值

(2)兩個變量出現同名

!function(){
  console.log(f); // undefined
  var f = 123;
  var f = 456;
  console.log(f); // 456
}()

結論
兩個變量出現同名,重複的var聲明無效,會被忽略,只會起到賦值的做用,前面賦值會被後面的覆蓋

(3)兩個函數出現同名

!function(){
  console.log(f); // function f(){return 456};
  function f(){return 123};
  function f(){return 456};
  console.log(f); // function f(){return 456};
}()

結論
兩個函數出現同名,因爲javascript中函數沒有重載,前面的同名函數會被後面的覆蓋

(4)變量名與參數名相同

函數參數的本質是什麼?函數參數也是變量,至關於在該函數的執行環境內最頂部聲明瞭實參

狀況一:參數爲變量,與變量命名衝突

(function (a) {
    console.log(a); // 100
    var a = 10;
    console.log(a); // 10
})(100);
// 至關於
(function (a) {
  var a = 100;
    var a; // 重複聲明的var沒有意義,忽略
    console.log(a); // 100
    a = 10;
    console.log(a); // 10
})(100);

狀況二:參數爲函數,與變量命名衝突

(function (a) {
    console.log(a); // function(){return 2}
    var a = 10;
    console.log(a); // 10
})(function(){return 2});
// 至關於
(function (a) {
  var a = function(){return 2};
    var a; // 重複聲明的var沒有意義,忽略
    console.log(a); // function(){return 2}
    a = 10;
    console.log(a); // 10
})(function(){return 2});

狀況三:參數爲空,與變量命名衝突

(function (a) {
    console.log(a); // undefined
    var a = 10;
    console.log(a); // 10
})();
// 至關於
(function (a) {
  var a;
    console.log(a); // undefined
    a = 10;
    console.log(a); // 10
})();

結論
1.重名的是一個變量,傳入了參數(變量或函數):
若是是在變量賦值以前,此時獲取的是:參數的值!

若是是在變量賦值以後,此時獲取的是:變量的值!

2.重名的是一個變量,可是沒有傳入參數:

若是是在變量賦值以前,此時獲取的是:undefined!
若是是在變量賦值以後,此時獲取的是:變量的值!

(5)函數名與參數名相同

狀況一:參數爲變量,與函數命名衝突

(function (a) {
    console.log(a); // a(){}
    function a(){};
    console.log(a); // a(){}
})(100);
// 至關於
(function (a) {
  var a = 100;
  function a(){};
    console.log(a); // a(){}
    console.log(a); // a(){}
})(100);

狀況二:參數爲函數,與函數命名衝突

(function (a) {
    console.log(a); // function a(){return 1}
    function a(){return 1}
    console.log(a); // function a(){return 1}
})(function(){return 2});
// 至關於
(function (a) {
  var a = function(){return 2};
  function a(){return 1};
    console.log(a); // function a(){return 1}
    console.log(a); // function a(){return 1}
})(function(){return 2});

狀況三:參數爲空,與函數命名衝突

(function (a) {
    console.log(a); // function a(){};
    function a(){};
    console.log(a); // function a(){};
})();
// 至關於
(function (a) {
  var a;
  function a(){};
    console.log(a); // function a(){};
    console.log(a); // function a(){};
})();

注意:函數表達式聲明方式var f = function(){},在預解析中與普通的變量聲明無異,由於預解析階段主要是經過varfunction來區分函數與變量的

結論
傳參爲函數時,賦值前均爲參數的值,賦值後爲函數的值,傳空時均爲函數的值

如今,咱們回到變量對象中,在變量對象的建立階段(執行流編譯階段),分別肯定了arguments對象、函數聲明和變量聲明,其優先級也是arguments>function>var,arguments就是參數,反推上面變量提高的結論,一樣是成立的

經典推薦:《javascript高級程序設計》筆記:原型圖解

相關文章
相關標籤/搜索