changed 6 years ago
Linked with GitHub

Scope 作用域

tags: javascriptconcept

每個變數會依據其生存範圍,也就是作用域來呈現最後的結果。

var a = 20 
function test() {
  var a = 10
  console.log(a) // 10
}

test()
console.log(a) // 20

上面的例子,var a = 20 是一個全域變數,var a = 10 是在 function 為test的作用域當中設置的變數,因此在 test 函式裡面的 console.log(a) 會先在 test函式尋找有沒有符合的 a 變數,如果沒有,接著才會去尋找更外面的作用域是否有a變數,因此 test() 回傳的結果是var a = 10 設置的10。而第二個console.log(a)的結果為全域變數 var a = 20 設置的 20

var a = 20 
function test() {
  a = 10
  console.log(a) // 10
}

test()
console.log(a) // 10

上面的例子只是把原本的var a = 10var拿掉,只剩下a = 10,也就是說 a 沒有被重新宣告,所以說當test()呼叫函式的時候,因為在test當中沒有看到 a 被宣告,因此會往上找a有沒有被宣告,發現全域變數有宣告過了,因此把那個全域變數a的值改成10,所以之後拿取a的值都會是10

作用域鍊(scope chain)

試問以下三個 console.log() 答案為何。

var a = 'global'
function test(){
  var a = 'test scope a'
  var b = 'test scope b'
  console.log(a, b)
  function inner() {
    var b = 'inner scope b'
    console.log(a, b)
  }
  inner()
}

test()
console.log(a)
  1. test scope a test scope b
  2. test scope a inner scope b
  3. global

inner 這個 function 當中,因為它的作用域當中本身沒有 a,因此它往上一層作用域找,找到 var a = 'test scope a'所定義的 a,因此採用它,而 b 變數在它的作用域當中本身就有了,所以直接使用自身的b

假如把var a = 'test scope a'這個去掉:

var a = 'global'
function test(){
  var b = 'test scope b'
  console.log(a, b)
  function inner() {
    var b = 'inner scope b'
    console.log(a, b)
  }
  inner()
}

test()
console.log(a)

innera 值一樣先從自身作用域找,沒找到,因此尋找 test 的作用域,也沒有找到,所以繼續往上一層找,也就是全域變數的作用域,找到了,所以使用global 作為a的值。

而這一連串的找法就像一個作用域鍊一樣:inner scope -> test scope-> global scope。都是先從自身的 scope 開始找起,假如找不到就繼續往接著上一層的 scpoe 尋找,直到找到值為止。

var a = 'global'
function change(){
  var a = 'change'
  function inner() {
    var a = 'inner'
    test()

  }
  inner()
}

function test() {
  console.log(a)
}

change()

這個例子的console.log(a)結果會是 global,也就是全域變數a的值,因為 test scope chain 實際上是: test scope -> global scope,所以test在自身那一層沒找到的話就會跑去尋找 global scope 的那一層。換句話說,scope chain 並不會根據你在哪邊呼叫它而有所變化,而是在定義這個 scope 的時候它所處在的程式碼位置即已定案。

ES6 以後的作用域差別

function test() {
  var a = 60 
  if (a === 60) {
    var b = 10
  }
  console.log(b)
}

test()

上面這個例子的變數宣告一樣是使用var對於var來說,它的作用域範圍以 function 為主,所以在 function 裡面且在 if block 裡面的b變數其實在test當中都抓得到資料。

而,從 ES6 開始加入了letconst兩個變數宣告型式,它們的作用域是以 block 為主,也就是{ }大括弧,因此像上面的例子如果改成這樣:

function test() {
  var a = 60 
  if (a === 60) {
    let b = 10
  }
  console.log(b)
}

test()

結果會回傳b is not defined ,因為 if block 裡面的let變數在外面的作用域其實抓不到。

Select a repo