什麼是JavaScript的Scope,弄懂Scope的規範,才能避免不必要的bug

今天要來了解一下一個很重要小細節概念就是Scope,了解Scope運作的概念,才能知道你的變數使用的規則,不然就會發生,我變數宣告了,為什麼不能用的情況

什麼是Scope

Scope又被稱為作用域/範疇…等,是JavaScript在查找變數的一套規則。就是

變數可以被取用,被看到的有效範圍

在ES6以前只有分為Global Scope和Function Scope

在ES6之後就新增了let跟const的宣告方式,也就增加了Block Scope

而Function Scope跟Block Scope可以合稱為 Local Scope

Global Scope

全域作用域,這是指不在Function Scope或Block Scope裡面的變數,只要在Global Scope宣告,任何地方都可以使用這個的變數

EX:

我們宣告globalText的變數,就是在Global Scope環境宣告的,在fun的funcion內跟外執行顯示globalText

var globalText = 'globalText'

function fun() {
            console.log('在function裡面', globalText) //在function裡面 globalText
}
fun()
console.log('在function外面', globalText) //在function外面 globalText

得到的結果就是兩個都能執行成功

Function Scope

函式作用域為Local Scope的其中之一,這顧名思義就是在function(函式)內才可以使用的”變數”跟”function(函式)”,只要function之外就不可以使用

EX:

我們先來看一段正常執行的程式

在fun()這個function中宣告了funText的變數,也在fun() 裡面執行了funText;然後宣告了sayHello() 的function在fun()裡面,同時也在fun()裡面執行了sayHello() 

function fun() {
      var funText = 'funText'
      console.log('在function裡面', funText) //在function裡面 funText

      function sayHello() {
           console.log('在fun裡面 說你好')   //在fun裡面 說你好
      }
     sayHello()
}
fun()


得到的結果也是兩邊的執行成功

那差在哪裡呢,就是差在如果把function裏面的變數或是函式拿到外面來,就會顯示錯誤

EX:

在fun()這個function中宣告了funText的變數,但在”fun() 外面”執行了funText;然後宣告了sayHello() 的function在fun()裡面,但也在”fun()外面”執行了sayHello() 

 function fun() {
     var funText = 'funText'

      function sayHello() {
                console.log('在fun裡面 說你好')
       }
}
fun()

console.log('在function裡面', funText) //Uncaught ReferenceError: funText is not defined
sayHello()  //Uncaught ReferenceError: sayHello is not defined


得到的結果就是錯誤訊息

Block Scope

區塊作用域為Local Scope的其中之一,在ES6之前,就只有function可以建立Scope,在ES6後就可以在大括號內建立Scope,而同樣的在大括號外就不可以使用, if 判斷式 、while 或是 for 迴圈語法用到的大括號範圍就是 Block

而要使用這個方式,就要使用文章開頭提到的let、const的宣告

先來看成功執行的部分

分別宣告varText、letText、constTexty在大括號內,也同樣的在括號內執行三個變數

 {
     var varText = "varText"
     let letText = "letText"
     const constText = "constText"

     console.log('在括號內執行', varText)   //在括號內執行 varText
     console.log('在括號內執行', letText)   //在括號內執行 letText
     console.log('在括號內執行', constText) //在括號內執行 constText
}

執行結果是成功執行

那如果將所有的變數移到大括號外使用呢

分別宣告varText、letText、constTexty在大括號內,並且將”在括號外”執行三個變數

{
     var varText = "varText"
     let letText = "letText"
     const constText = "constText"
}

     console.log('在括號內執行', varText)   //在括號內執行 varText
     console.log('在括號內執行', letText)   //Uncaught ReferenceError: letText is not defined
     console.log('在括號內執行', constText) //Uncaught ReferenceError: constText is not defined  

得到的結果是只有var宣告的變數,不會被限制在括號內,而如果要使用block Scope的話,就必須要用let跟const去宣告

宣告 Local Scope的好處

  • 最小權限原則
  • 避免衝突

最小權限原則

避免被不當存取,他存的變數是其他碼不需要知道的資料,所以對於其他地方是無法直接存取或使用的,只能透過公開處理過的方式取得,這樣可以避免被人串改或是不當利用

避免衝突

假設有一個變數或函式在不同區域都要被利用到,但是兩個存取的資料是不同的,在這狀態下則可以避免衝突

EX先示範一個發生衝突的

在Global Scope(全域作用域)中,我們設定一個sumNum,並且設定一個sumNum加10次的函式plusNum(),跟sumNum減10次的函式reduceNum()

let sumNum = 0
function plusNum() {
     for (i = 0; i < 10; i++) {
         sumNum++
    }
    console.log('plusNum函式呼叫結果', sumNum) //plusNum函式呼叫結果 10
 }

function reduceNum() {
     for (i = 0; i < 10; i++) {
         sumNum--
     }
     console.log('reduceNum函式呼叫結果', sumNum) //reduceNum函式呼叫結果 0
}

plusNum()
reduceNum()

得到的結果是,因為sumNum在Global Scope宣告,所以一開始被加10次得到是10,但在減的時候被呼叫過去從原本的10開始減10次變成0

再以同樣例子我們去把他都丟到自己的函式宣告

設定一個宣告sumNum,並且sumNum加10次的函式plusNum(),跟宣告sumNum,並且sumNum減10次的函式reduceNum()

function plusNum() {
     let sumNum = 0
     for (i = 0; i < 10; i++) {
           sumNum++
     }
    console.log('plusNum函式呼叫結果', sumNum) //plusNum函式呼叫結果 10
}

function reduceNum() {
      let sumNum = 0
      for (i = 0; i < 10; i++) {
           sumNum--
      }
   console.log('reduceNum函式呼叫結果', sumNum) //reduceNum函式呼叫結果 -10
}

plusNum()
reduceNum()

得到的結果是plusNum()結束後一樣獲得加10次的結果,reduceNum()則是變成-10的結果,兩邊的結果不會互相衝突

結論

這在寫程式中會有很多小細節的錯誤,有時候會摸不清頭緒,明明邏輯正確,得到的資料卻天差地遠,可能就是沒搞清楚變數在不同Scope的規範是什麼

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *