標籤:color var 對象 enc strong undefined 全域 not 定義
1、JS範圍
在ES5中,js只有兩種形式的範圍:全域範圍和函數範圍,在ES6中,新增了一個塊級範圍(最近的大括弧涵蓋的範圍),但是僅限於let方式申明的變數。
2、變數聲明
1 var x; //變數聲明2 var x=1; //變數聲明並賦值3 x = 1; // 定義全域變數並賦值
3、函式宣告
function fn(){}; //函式宣告並定義var fn = function(){}; // 實際上是定義了一個局部變數fn和一個匿名函數,然後把這個匿名函數賦值給了fn
4、變數提升
var tmp = new Date();function fn(){ console.log(tmp); //Wed Jul 12 2017 22:11:56 GMT+0800 (中國標準時間)}
fn();
a情形
var tmp = new Date();function fn(){ console.log(tmp); //undefined if(false){ var tmp = ‘hello‘; }}fn();
b情形
var tmp = new Date();function fn(){ console.log(tmp); //undefined if(true){ var tmp = ‘hello‘; }}fn();
c情形
從上面可以看到,b情形和c情形為什麼不同於a情形,就是因為變數提升了(ps: c情形不同於b情形的是判斷條件為true,但是這裡不是看代碼有沒有被執行,是看變數有沒有被定義)。fn函數裡面定義了同名變數tmp,無論在函數的任何位置定義tmp變數,它都將被提升到函數的最頂部。等同於下面情形:
var tmp = new Date();console.log(tmp);function fn(){ var tmp; console.log(tmp); //undefined if(false){ var tmp = ‘hello‘; }}fn();
這裡需要說明的是,雖然所有的申明(包括ES5的var、function,和ES6的function *、let、const、class)都會被提升,但是var、function、function *和let、const、class的的提升卻並不相同!具體原因可以看這裡的說明(大體的意思是雖然let,const,class也被提升了,但是卻並不會被初始化,這時候去訪問他們則會報ReferenceError異常,他們需要到語句執行的時候才會被初始化,而在被初始化之前的狀態叫做temporal dead zone)。
因為這樣的原因,推薦的做法是在申明變數的時候,將所用的變數都寫在範圍(全域範圍或函數範圍)的最頂上,這樣代碼看起來就會更清晰,更容易看出來那個變數是來自函數範圍的,哪個又是來自範圍鏈。
5、重複聲明
var x = 1;console.log(x);if(true){ var x = 2; console.log(x);}console.log(x);
上面的輸出其實是:1 2 2。雖然看起來裡面x申明了兩次,但上面說了,js的var變數只有全域範圍和函數範圍兩種,且申明會被提升,因此實際上x只會在最頂上開始的地方申明一次,var x=2的申明會被忽略,僅用於賦值。也就是說上面的代碼實際上跟下面是一致的:
var x = 1;console.log(x);if(true){ x = 2; console.log(x);}console.log(x);
6、函數和變數同時提升的問題
console.log(fn);function fn(){};var fn = ‘string‘;
上面的輸出結果其實是: function fn(){}
,也就是函數內容。
console.log(fn);var fn = function fn(){};var fn = ‘string‘;
這時輸出結果就是undefined,知道上面的聲明提升的道理就不難理解了。
總結:
要徹底理解JS的範圍和Hoisting,只要記住以下三點即可:
1、所有申明都會被提升到範圍的最頂上
2、同一個變數申明只進行一次,並且因此其他申明都會被忽略
3、函式宣告的優先順序優於變數申明,且函式宣告會連帶定義一起被提升
注意:
通過with語句,可以臨時改變運行期內容相關的範圍鏈,此時的對非var定義的變數進行訪問,會首先訪問with中對象的屬性,然後才會向上順著範圍鏈向上檢查該屬性。
js變數範圍--變數提升