iT邦幫忙

2021 iThome 鐵人賽

DAY 8
0
Modern Web

JavaScript學習日記系列 第 8

JavaScript學習日記 : Day8 - 作用域(Scope)

作用域即函數或變數的可見區域,白話點就是,函數或變數不在這個區域內,就無法獲取到。

1. 函數作用域

用函數形式 function() {…}類似的代碼包起來的部分,即函數作用域。

與函數作用域相對應的概念是全局作用域,也就是定義在最外層的變數或函數,可以在任何地方訪問他們。

let a = 123 // 全域作用域

function func() {
    var b =  456
    console.log(a) 
}

console.log(a) // apple

console.log(b) // Uncaught ReferenceError: b is not defined

func() //123

另一個例子 :

// 全域作用域

function func() { //作用域A
    let a = "coffee"
    
    function func1() { //作用域B
        let a = "apple"
        let b = "banana"
        // 這裡可放許多要對外隱藏的變數
        
        console.log(a);
    }
    
    console.log(a) // coffee
    console.log(b) // Uncaught ReferenceError: b is not defined
    func1() //apple
}

func();

等於有外層函數func的作用域A內嵌了函數func1的作用域B。在func裡面的console.log(a)訪問變數a時,JS引擎會先從離自己最近的作用域A查找變量a,找到就不再繼續查找,找不到就去上層作用域(此例中上層作用域是全域作用域)繼續查找,此例中a已經找到且值為"coffee",所以打印輸出coffee。依此類推,執行func1(),會執行func1函數內部的console.log(a),隨即會在作用域B查找裡面a,而作用域B裡面存在一個a的聲明和賦值語句let a = “apple” ,所以最先找到a的值是apple,找到便不再繼續查找,最終func1()輸出apple而不是coffee。

圖解上面程式碼的作用域:

2. ES6帶來的Object Scope

在{}內用let關鍵字聲明的變數與函式(表達式)屬於塊級作用域(Object scope)。

但是這對ES6以前的代碼顯然產生很大的影響,出於兼容性的問題,在塊級聲明的函數依然可以在外部取用,如果需要函數只在塊級中取用,應該使用let關鍵字寫成函數表達式。以下例子:

 function test() {
        {
            function inner() {
                alert('inner function')
            } 
        }
        inner()
    }
    
    test() // inner function

上面例子證明JS引擎為了兼容在ES6實現中做了變通的處理,在看以下例子:

function run() {
  var foo = "Foo";
  let bar = "Bar";

  console.log(foo, bar); // Foo Bar

  {
    var moo = "Mooo"
    let baz = "Bazz";
    console.log(moo, baz); // Mooo Bazz
  }

  console.log(moo); // Mooo
  console.log(baz); // ReferenceError
}

run();

上面的例子用let聲明的函數,才是真正的塊級作用域。

3. 為什麼要新增Object scope?

3.1 var聲明存在副作用 --- Hoisting

之前文章也有提到過,常理是先聲明後使用,而var卻允許先使用後聲明:

console.log(a); // undefined
console.log(b); // ReferenceError
var a = "car"; // 聲明提前
let b = "123"; //由let聲明的不存在提前特性

3.2 var聲明變數有汙染疑慮

for(var i = 0; i < 100; i++) {
        // many code
    }
    
    // many code
    console.log(i) // 100

循環裡面的i在循環結束後,並沒有回收掉,而是一直存在的垃圾變數,汙染了當前的環境。

for(let i = 0; i < 100; i++) {
        // many code
    }
    
    // many code
    console.log(i) // ReferenceError

所以應該使用let,避免使用var,除非目的是想要定義一個全域變數。

參考資料:
函式與作用域
Scope 作用域
Javascript 的作用域 (Scope) 與範圍鏈 (Scope Chain)


上一篇
JavaScript學習日記 : Day7 - 函數(二)
下一篇
JavaScript學習日記 : Day9 - 執行環境(Execution Context)
系列文
JavaScript學習日記30

尚未有邦友留言

立即登入留言