詳解變量聲明加 var 和不加 var 的區別


在全局作用域中聲明變量加 var 關鍵字和不加 var ,js 引擎都會將這個變量聲明為全局變量,在實際代碼運行時,兩種聲明方式的變量的行為也幾乎是一致的。但是在全局作用域下是否聲明一個變量的時候加 var 和不加 var,js 引擎具體執行了哪些操作呢,其效果又是否完全一致?

首先我們看在一個函數體內(局部作用域)聲明變量,如下:

// 變量聲明不加 var
function foo (a) { console.log(a + b) // b is not defined
  b = a } foo(2)

【分析】執行 foo(2) 的時候,我們具體看 foo 函數,首先打印了 a + b 的值,然后聲明了一個 b 變量(沒有使用var關鍵字),並將傳入的 a 賦值給 b,因為 js 引擎按照代碼順序編譯和執行代碼,因此在打印 a + b 的時候,在任何作用域中都是無法找到 b 變量的。

在執行 foo(2) 表達式的時候 js 引擎具體的的操作過程如下:

  1. 在當前作用域中查找名為 foo 的函數(RHS)
  2. 進入 foo函數體,首先 JS 引擎在執行前會對整個腳本文件的聲明部分做完整分析(包括局部變量),從而確定變量的作用域(js引擎讀取一段js代碼,首先執行預解析,就是逐行讀取js代碼,尋找全局變量和全局函數,遇到全局變量,把變量的值變為undefind,存在內存中,遇到全局函數,直接存在內存中,這個過程如果發現語法錯誤,預解析終止)。因此第一步搜集變量,發現在函數作用域中這里只有作為參數的局部變量 a,提升到作用域頂部
  3. 將 2 賦值給參數變量 a(a = 2, LHS)
  4. 查找 console 對象(RHS),發現是內置函數,在 console 對象下查找 log 函數(RHS)
  5. 在當前作用域中查找變量 a,並獲取 a 的值為(a = 2, RHS)
  6. 在當前作用域中查找變量 b,未找到該變量
  7. 將 a 和 b 的查找結果傳入 console.log() 函數,打印結果( b 未定義,拋出錯誤: b is not defined)
  8. 繼續執行 b = a。首先獲取變量 a 的值(a = 2, LHS), 然后在當前作用域中查找變量 b(RHS),未找到,到上一層作用域(全局作用域)中查找(RHS),未找到,
  9. 在全局作用作用域中創建一個名稱為 b 的變量,並將其返回給引擎(注意:嚴格模式下禁止自動或隱式地創建全局變量)
  10. 將 a 的值(2)賦值給全局變量 b( b = a, LHS)

再看第二個例子

// 變量聲明加 var
function foo (a) { console.log(b) // undefined
  console.log(a + b) // NaN
  var b = a } foo(2)

【分析】執行 foo(2) 的時候,我們看 js 引擎具體做了哪些操作?

  1. 在當前作用域中查找名為 foo 的函數(RHS)
  2. 進入 foo 函數體,搜集變量,發現聲明了局部變量 a 和 b,因此將 a 和 b 提升到函數作用域的頂部(此時 b 的值為 undefined)
  3. 參數賦值,將 2 賦值給變量 a(a = 2, LHS)
  4. 查找 console 對象(RHS),發現是內置函數,在 console 對象下查找 log 函數(RHS)
  5. 在當前作用域中查找變量 b(RHS),發現已經聲明,但是值為 undefined ,傳給log()函數,執行打印,輸出結果(b is not defined)
  6. 重復第4步 RHS 查找 console.log() 函數,查找變量 a 的值(a = 2, RHS);查找變量b的值(b = undefined, RHS),將 a 和 b 的值傳入 console.log(),執行運算,輸出結果(2 + undefined,結果 NaN)

通過上述在函數體內聲明變量的例子,已經可以看出來 js 引擎在處理這兩種情況的區別,在全局作用域中的也是如此,而且理解起來更為簡單。

看兩個例子:

console.log(a) // undefined
a = 3

聲明變量 a 的時候沒有加 var,因此 js 引擎默認將變量 a 聲明為全局變量(值為 undefined)並提升到作用域頂部(為什么在console.log() 中可以訪問到 a),但是此時的賦值操作需要等到 console.log() 方法執行完之后才會執行,因此在 console.log(a) 打印的結果會是 undefined

console.log(a) // undefined
var a = 3

這里的輸出結果仍舊是 undefined,但是和上面的例子不同的是, js 引擎並沒有主動的去創建變量 a,而是直接將變量 a 搜集到全局變量的集合中,並將 a 提升到作用域頂部。

【延伸】全局變量是 window 的屬性(瀏覽器環境下),因此聲明全局變量時是否加 var,可以通過 Object 提供的 getOwnPropertyDescriptot(object, propertyName) 來進行比較是否存在不同:

var a = 1
b = 2
console.log(Object.getOwnPropertyDescriptor(window, a)) // { value: 1, writable: true, enumerable: true, configurable: false }

console.log(Object.getOwnPropertyDescriptor(window, b)) // { value: 2, writable: true, enumerable: true, configurable: true } 

通過結果的比較可以發現,未使用 var 聲明的全局變量的configurable 屬性是 true,也就是說,未通過 var 聲明的變量是可以刪除的,如下:

delete a
// false

delete b
// true

*關於 Object.getOwnPropertyDescriptor(object, propertyName) (propertyName 需要傳入字符串形式的屬性名)

【參考:深入理解javascript對象系列第三篇——神秘的屬性描述符

用於查詢一個屬性的描述符,並以對象的形式返回,返回結果的屬性如下:

  • Configurable:是否可以使用 delete 刪除屬性,以及是否可以修改屬性描述符的特性,默認值為 true
  • Enumerable:是否出現在對象的屬性枚舉中,比如是否可以通過 for-in 循環返回該屬性,默認值為 true
  • Writable:是否可以修改屬性的值,默認值為 true
  • Value:屬性的數據值,讀取屬性值的時候,從這個位置讀;寫入屬性值的時候,把新值保存在這個位置。默認值為 undefined

 

【補充知識點】

LHS 和 RHS

全局變量和局部變量

作用域、作用域鏈、預解析

更多變量聲明是否加 var 的區別

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM