1. 程式人生 > >函數作用域

函數作用域

讀取 fun 局部變量 需要 它的 第一個 span error 容易

作用域的概念對於初學者來說可能比較難,它涉及到變量,函數等基礎知識,理解作用域對於理解作用域鏈和閉包是非常重要的,今天閑來一起復習下作用域:

1、定義

作用域(scope)指的是變量可訪問的範圍,在JavaScript中只有兩種作用域:一個全局作用域,另一個是函數作用域。全局作用域指變量在任何函數外顯式聲明,那麽這樣的話,在當前作用域下的任何地方都可以訪問到這個全局變量;至於函數作用域,指的是變量在函數內部顯式聲明,這個變量只能在函數內部內被訪問到,函數外部無法訪問;

在全局聲明的變量成為全局變量,在函數內部定義的變量成為局部變量,局部變量只能在函數內部讀取;

var v = 1;

function
f(){ console.log(v); } f()// 1

在上面的代碼中,變量v是全局中聲明,為全局變量,在函數內就能讀取到,所有輸出結果為1

然鵝,在函數內部定義的變量為局部變量,在函數外無法讀取哦,如下:

function f(){
  var v = 1;
}
//輸出變量v console.log(v)
// ReferenceError: v is not defined

在上面的代碼中,由於變量v是在函數f內部定義的,所以在函數外部無法讀取到,因此會報undefined哦;

如果在函數外部和內部同時出現同名變量呢?在函數內部讀取的應該是全局的還是局部的呢?答案是局部變量,函數內部的局部變量會覆蓋同名全局變量:

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

在上面代碼中,就是讀取了一個函數內部的隱式全局變量,這個變量在哪裏都可以訪問到;

2、變量提升

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;

好,接下來,我們看看函數的聲明提升,先看看函數有常見的幾種聲明方式:

  1. 使用function命令聲明,如:function f1(){};
  2. 使用變量賦值方式聲明,也叫函數表達式,如var f1 = funtion(){};
  3. 使用Function函數創建實例聲明,如var f1 = new Function();(此方法不常用)

看第一個,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;

至於第三種,和第二種一樣,也是變量賦值形式,普通的變量提升;

3、函數本身的作用域

函數本身也是一個值,也有自己的作用域。它的作用域與變量一樣,就是其聲明時所在的作用域,與其運行時所在的作用域無關。

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,這也是前面講的,內部同名變量會覆蓋全局變量,這也是理解閉包的基礎哦!

今天到這裏了,學習不停。。。。

函數作用域