Web開發學習筆記06 — Function Scope vs. Block Scope、IIFE


Posted by Teagan Hsu on 2020-12-09

函式作用域(Function Scope)

函式作用域內的變數、函式僅能在這個函式(Function)裡面使用。以下範例發現在全域環境作用域中讀取不到函式作用域中的phoneNumberbirthDayage,但可以存取到myFunc()

function myFunc(){
var phoneNumber = "091234567";
var birthDay = "10/31";
var age = 18;
console.log(phoneNumber);
console.log(birthDay);
console.log(age);
}
var name = "Teagan";
console.log(phoneNumber);    //ReferenceError
console.log(birthDay);       //ReferenceError
console.log(age);            //ReferenceError
console.log(name);           //Teagan
myFunc() 
//091234567
//10/31
//18

函式宣告(Function Declaration)

傳參數

function yourName(name){
  var name = "Teagan";
  console.log(name);
}
yourName();
//Teagan

函式運算式(Function Expression)

用變數指定給函式

var yourName = function bar(name) {
    var name = "Teagan";
    console.log(name);
}
yourName();
//Teagan

IIFE(立即呼叫函式表達式)

IIFE可以幫助我們避免污染到全域變數,也可以讓函數在被宣告的那刻就立即執行。以下範例(還未賦值):

(function foo(num){
console.log(`This is ${num}`);}
)();
//This is undefined

如果我們要賦值的話可在()中加入值,如以下範例:

(function foo(num){
console.log(`This is ${num}`);}
)(100);
//This is 100

IIFE可以幫助我們避免污染到全域變數,在global scopefirstName就無法干擾到函式裡的firstName

//global scope
var firstName = "Wu";
//function scope
(function yourName(name) {
var firstName = "Hsu";
console.log(`Hello! ${name} ${firstName}`)
})("Teagan");
//Hello! Teagan Hsu

反之,如果要在IIFE中傳入全域變數,只要傳入(window)即可:

var dinner = 'beef';
(function food(global){
    var dinner = 'vegetable';
    console.log(`My dinner is ${dinner}`);
    console.log(`My dinner is ${global.dinner}`);
})(window);

//My dinner is vegetable
//My dinner is beef

名稱相同造成的衝突與錯誤

函式宣告與函式運算式可能會因為不當命名,造成衝突與錯誤,像是以下的例子:

function i(a) {
    b = j(a * 3);
    return (b);
};
function j(a) { return a - 3; };
function j(a) { return a - 1; };
var b;
i(100);
//299

解決方法

①function裡再包入一個function

function i(a) {
    b = j(a * 3);
    return b;
function j(a) { return a - 3; }
};
function j(a) { return a - 1; };
var b;
i(100);
//297

②使用IIFE

(function i(a) {
    b = j(a * 3);
    return b;
})(function j(a) { return a - 3; });
//傳值function j(a) { return a - 3; }

function j(a) { return a - 1; }
var b;
i(100);
//297

經典題目

使用setTimeout打印出0、1、2、3、4*
setTimeout函數的回調(callback)不會被觸發直到for loop執行完成。也就是說for loop執行完成後,才會執行回調函數。for loop完成後i的值為5,當執行回調(callback)函式時,函式使用的i值會是最後跑出的5,這也就是為什麼5會被執行5次。

for (var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
//5, 5, 5, 5, 5

解決方法

那我們要如何順利使用setTimeout打印出0、1、2、3、4呢?

①使用let

let可幫助我們解決這個問題,使用let時每次迭代都會創建一個單獨的作用域,可以打印連續的變量值(variable value),範例如下:

for (let i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
//0, 1, 2, 3, 4

②把for loop與setTimeout分開

一開始就不要把setTimeout包在for loop裡,而是分開寫程式以免污染全域作用域。

for (let i = 0; i < 5; i++) {
    timeoutID(i);
};

function timeoutID(i) {
    setTimeout(function() {
        console.log(i);
    }, 1000);
};
//0, 1, 2, 3, 4

③使用IIFE

同理,使用IIFE也可解決這個問題。

for (var i = 0; i < 5; i++) {
(function(x) {
setTimeout(function() {
console.log(x);
}, 1000);
})(i)
}
//0, 1, 2, 3, 4

區塊作用域(Block Scope(ES6))

ES6 以後可以使用新關鍵字constlet宣告區塊作用域(Block Scope)的變數。

const

const的特性是宣告時就必須賦值,而且賦值後不可修改其值,否則會報錯。通常使用在不再做更改的變數,像是:

const pi = 3.14

let

letvar的差別在於,var作用於function scope中,而let作用於block scope中。以下例子可以發現var name= "Amy"取代了原本的Teagan,因此console.log的結果會是//Amy。而使用let則不會有此問題,因為let只作用在大括弧中的區塊{…}
以var為例:

function varName () {
 var name="Teagan";
 if (true) {
 var name= "Amy"; 
 }
 console.log(name); 
}
varName()  //Amy

以let為例:

function letName () {
 let name = "Teagan";
 if (true) {
 let name = "Amy"; 
 }
 console.log(name); 
}

letName()  //Teagan

-
參考資料:

  1. Understanding setTimeout Inside For Loop In JavaScript - Code Handbook
  2. Watch Out When Using SetTimeout() in For Loop #JS
  3. 函式與作用域 - 從ES6開始的JavaScript學習生活
  4. 你懂 JavaScript 嗎?#12 函式範疇與區塊範疇(Function vs Block Scope)- Summer。桑莫。夏天
  5. 重新認識 JavaScript: Day 18 Callback Function 與 IIFE
  6. JavaScript: var, let, const 差異
  7. 鐵人賽:ES6 開始的新生活 let, const

#function scope #block scope #IIFE #let #const







Related Posts

NumPy 基礎 | 拆分陣列(Split Array)

NumPy 基礎 | 拆分陣列(Split Array)

Check If All 1's Are at Least Length K Places Away

Check If All 1's Are at Least Length K Places Away

人性較量Day01~圖靈測試(Turing test)

人性較量Day01~圖靈測試(Turing test)


Comments