本文是我學習JavaScript做用域整理的筆記,若有不對,請多指出。html
一個變量的做用域是程序源代碼中定義這個變量的區域。前端
而在ES5中只分爲全局做用域和函數做用域,也就是說for,if,while等語句是不會建立做用域的。ES6(let,const)除外。程序員
//全局做用域 var a = 123; function aa () { //局部做用域 var b = 456; }
JavaScript函數裏聲明的全部變量(但不涉及賦值)都被「提高」至函數體的頂部,在代碼開始運行以前。這個特性被稱爲聲明提早。segmentfault
var a = "g"; function f() { console.log(a); //輸出undefined var a = "l"; console.log(a); //輸出"l" }
因爲函數做用域的特性,局部變量在整個函數體始終是有定義的,也就是說,函數體的局部變量覆蓋了同名全局變量。在函數體內,變量a被「提早」了,提早至函數體的頂部,因此第一次輸出的是undefined,那時候還沒賦值,但代碼執行到var語句時候,局部變量纔會被賦值。所以第二次輸出則是「l」。此代碼過程以下:瀏覽器
var a = "g"; function f() { var a; console.log(a); //輸出undefined a = "l"; console.log(a); //輸出"l" }
所以一些程序員特地將變量聲明放在函數體的頂部,而不是將聲明靠近放在使用變量之處。函數
先看一段簡單代碼,代碼以下:學習
var name = "wythe"; function one () { console.log(name); //wythe var firend = "zero"; } one(); console.log(firend); //報錯
看到代碼可知,name是在全局做用域中聲明的全局變量,而firend則是在函數做用域中聲明的局部變量。在執行時候你會發現函數做用域可以訪問到在全局做用域中name這個變量,而全局做用域卻不能訪問到函數做用域的friend的變量,緣由是做用域鏈!
做用域鏈的規則:
外部不能訪問內部變量,內部能夠訪問外部變量
爲何會有這樣規則?由於是執行環境所規定的。this
執行環境定義了變量或函數有權訪問其餘數據,決定了它們的行爲。每一個執行環境都有一個與之關聯的變量對象(variable object),環境中定義的全部變量和函數都保存在這個對象中。
全局執行環境是最外圍的一個執行環境。在Web瀏覽器中,全局執行環境被認爲是window對象。某個執行環境中全部全部代碼執行完畢後,該環境被銷燬,保存在其中的全部的變量和函數定義也隨之銷燬。spa
補充說明:須要瞭解一些概念,變量對象(Variable Object)、活動對象(Activation Object)、函數的屬性[[scope]].設計
變量對象指的是變量對象(縮寫爲VO)是一個與執行上下文相關的特殊對象,它存儲着在上下文中聲明的內容有:變量 (var, 變量聲明)、函數聲明和函數的形參。
執行上下文(執行環境):每次當控制器轉到ECMAScript可執行代碼的時候,即會進入到一個執行上下文。執行上下文(簡稱-EC)是ECMA-262標準裏的一個抽象概念,用於同可執行代碼(executable code)概念進行區分。
活動對象指的是由函數的運行期上下文(代碼執行前)建立,在運行時可變,初始時只有 arguments 屬性,經過變量的初始化,包含了局部變量、命名參數、 this 等
函數屬性[[scope]]指的是函數對象都有一個內部屬性 [[scope]],函數被建立後,函數 [[scope]] 屬性會被建立此函數的做用域中可訪問的數據對象填充,是全部父變量對象的層級鏈。[[scope]] 在函數被建立時靜態存儲,永遠不會改變,直至銷燬。
每一個函數都有本身的執行環境。當執行流進入一個函數時,函數的環境就會被推入一個環境棧中。而在函數執行以後,棧將環境彈出,把控制權返回以前的執行環境。當代碼在一個環境執行時候,會建立變量對象的一個做用域鏈(scope chain)。做用域的前端,始終都是當前執行的代碼所在環境的變量對象。如何這個環境是函數,則將其活動對象(activation object)做爲變量對象。活動對象在最開始只包含一個變量,即arguments對象(這個對象在全局環境是不存在)。做用域鏈中的下一個變量對象來自包含(外部)環境,而再下一個變量對象則來自下一個包含對象。這樣,一直延續到全局執行環境;全局執行環境的變量對象始終都是做用域中的最後一個對象。
根據這個概念圖解上面代碼:
在函數one建立時,它的做用域鏈中會填入一個全局對象,該全局對象包含了全部全局變量,當執行流執行到one()語句時,會建立函數one執行環境。將函數one執行環境。若是這個環境是函數,則建立一個活動對象,而後此對象會被推入做用域鏈的前端,當函數執行完畢後,活動對象也隨之銷燬。新的做用域鏈以下圖所示:
標識符解析是沿着做用域一級一級地搜素標識符的過程。搜素過程始終從做用域的前端開始,而後逐級地向後回溯,直到找到標識符爲止,找不到,會致使錯誤發生。內部環境能夠經過做用域鏈訪問全部外部環境,但外部環境不能訪問內部環境中任何變量和函數。這些環境之間的聯繫是線性,有次序的。
這是初步瞭解做用域,如想更深刻了解做用域,請看下面連接:
JavaScript做用域原理
JavaScript做用域鏈
由一道題圖解JavaScript的做用域或者看《JavaScript權威指南》和《JavaScript高級程序設計》