做用域的概念對於初學者來講可能比較難,它涉及到變量,函數等基礎知識,理解做用域對於理解做用域鏈和閉包是很是重要的,今天閒來一塊兒複習下做用域:閉包
一、定義
做用域(scope)指的是變量可訪問的範圍,在JavaScript中只有兩種做用域:一個全局做用域,另外一個是函數做用域。全局做用域指變量在任何函數外聲明(若是在函數內部聲明的時候沒有用var關鍵字,那就成了隱式全局聲明瞭,也是全局變量,在嚴格模式下是禁止的),那麼這樣的話,在當前做用域下的任何地方均可以訪問到這個全局變量;至於函數做用域,指的是變量在函數內部顯式聲明,這個變量只能在函數內部內被訪問到,函數外部沒法訪問;函數
在全局聲明的變量成爲全局變量,在函數內部定義的變量成爲局部變量,局部變量只能在函數內部讀取;學習
var v = 1; function f(){ console.log(v); } f()// 1
在上面的代碼中,變量v是全局中聲明,爲全局變量,在函數內就能讀取到,全部輸出結果爲1spa
然鵝,在函數內部定義的變量爲局部變量,在函數外沒法讀取哦,以下:code
function f(){ var v = 1; }
//輸出變量v console.log(v) // ReferenceError: v is not defined
在上面的代碼中,因爲變量v是在函數f內部定義的,因此在函數外部沒法讀取到,所以會報undefined哦;blog
若是在函數外部和內部同時出現同名變量呢?在函數內部讀取的應該是全局的仍是局部的呢?答案是局部變量,函數內部的局部變量會覆蓋同名全局變量:ip
var v = 1;//全局變量 function f(){ var v = 2;//局部變量 console.log(v); } f() // 2 console.log(v); // 1
在上面的代碼中,在全局和函數f中都有定義變量v,可是因爲兩個變量同名了,並且在f內部讀取的變量v,全部局部變量v會覆蓋掉全局變量v;作用域
這裏有個點須要注意下,前面我一直說的是顯式聲明變量,若是在函數內部隱式聲明瞭變量,即沒有用var命令去聲明變量,這個時候這個變量就是全局變量,在開發中,必定要避免隱式聲明變量,解決辦法是在採起嚴格模式,嚴格模式下禁止隱式聲明變量,會報錯;開發
function fn(){
x = 5;
}; console.log(x);// 5
在上面代碼中,就是讀取了一個函數內部的隱式全局變量,這個變量在哪裏均可以訪問到;io
二、變量提高
JavaScript引擎的工做方式是,在代碼正式執行以前,會有預解析的過程,先解析(通讀)代碼,獲取全部被聲明的變量和函數,而後再一行一行地運行。這形成的結果,就是全部的變量的聲明語句和全部的函數在其做用域內,都會被提高到代碼的頭部,這就叫作變量提高(hoisting),看看栗子:
console.log(a);//undefined var a = 1;
上面的代碼輸出結果爲undefined,爲何?由於在預解析階段,變量a聲明被提高到了頂部,到執行console.時,a只是聲明瞭,並無到賦值(js代碼從上到下依次執行),此時不會報錯,由於存在變量提高,真正是這樣運行的:
var a; console.log(a);//undefined a = 1;
最後的結果是顯示undefined
,表示變量a
已聲明,但還未賦值。
請注意,變量提高只對var
命令聲明的變量有效,若是一個變量不是用var
命令聲明的,就不會發生變量提高,由於沒有用var聲明,這個變量就是隱式全局變量,在全局均可以訪問到,不會提高聲明:
console.log(b);//1
b = 1;
好,接下來,咱們看看函數的聲明提高,先看看函數有常見的幾種聲明方式:
看第一個,function命名聲明函數,f1爲函數名,這種方法聲明的函數在預解析階段也會發現聲明提高,注意和變量不一樣的是,函數聲明提高的時候,會同時賦值函數體:
f1();
function f1(){ //some code here };
以上代碼,雖然函數f1在聲明以前就調用了,但正常執行,沒毛病老鐵,其實這麼寫就至關於下面這樣:
function f1(){ //some code here }; f1();
這是和變量聲明不同的地方;
看第二種,第二種採用變量賦值的方式,這個時候就當函數體是一個值,並且函數在JavaScript中就是一個值,能夠被任意賦值,那麼就和普通變量聲明提早同樣了,在聲明提高的時候不會發生賦值行爲:
f1();//Uncaught TypeError: f1 is not a function var f1 = function(){
console.log("啊哈");
};
結果是f1報錯undefined;
至於第三種,和第二種同樣,也是變量賦值形式,普通的變量提高;
三、函數自己的做用域
函數自己也是一個值,也有本身的做用域。它的做用域與變量同樣,就是其聲明時所在的做用域,與其運行時所在的做用域無關。
var a = 1; var x = function () { console.log(a); }; function f() { var a = 2; x(); } f(); // 1
上面的代碼中,因爲x函數是定義在f函數外部的,因此x函數做用域綁定在外層,內部變量a
不會到函數f
體內取值,因此輸出1
,而不是2
。
總之,函數執行時所在的做用域,是定義時的做用域,而不是調用時所在的做用域。
很容易犯錯的一點是,若是函數A
調用函數B
,卻沒考慮到函數B
不會引用函數A
的內部變量。
再來看個具體栗子:
var x = function () { console.log(a); }; function y(f) { var a = 2; f(); } y(x);// ReferenceError: a is not defined
上面代碼將函數x
做爲參數,傳入函數y
。可是,函數x
是在函數y
體外聲明的,做用域綁定外層,所以找不到函數y
的內部變量a
,致使報錯。
一樣的,函數體內部聲明的函數,做用域綁定函數體內部。
function foo() { var x = 1; function bar() { console.log(x); } return bar; } var x = 2; var f = foo(); f() // 1
上面代碼中,函數foo中定義了一個函數bar,在取出函數bar並調用的時候,bar內部的x變量訪問的是外層函數foo中的變量x,而不是同名全局變量x,這也是前面講的,內部同名變量會覆蓋全局變量,這也是理解閉包的基礎哦!
今天到這裏了,學習不停。。。。