Scope และ Closures
var, let และ const
หัวข้อที่มีชื่อว่า “var, let และ const”JavaScript มี keyword การประกาศสามแบบ แต่ละแบบมีกฎ scoping ที่แตกต่างกัน:
| Keyword | Scope | Hoisted | Reassignable | Re-declarable |
|---|---|---|---|---|
var | Function | ใช่ (เป็น undefined) | ใช่ | ใช่ |
let | Block | ใช่ (TDZ) | ใช่ | ไม่ |
const | Block | ใช่ (TDZ) | ไม่ | ไม่ |
Block scope หมายความว่าตัวแปรมีอยู่เฉพาะระหว่างเครื่องหมาย {} ที่ใกล้ที่สุด Function scope หมายความว่าตัวแปรมีอยู่ตลอด function body ไม่ว่า block ด้านในจะเป็นอย่างไร
function example() { if (true) { var x = 1; // function-scoped — รั่วออกนอก if let y = 2; // block-scoped — อยู่แค่ใน if const z = 3; // block-scoped } console.log(x); // 1 — มองเห็นที่นี่ console.log(y); // ReferenceError: y is not defined}Hoisting
หัวข้อที่มีชื่อว่า “Hoisting”การประกาศตัวแปรถูก hoist — เลื่อนไปที่ด้านบนของ scope โดย engine ก่อนการ execution
การ hoist ของ var จะ initialise ตัวแปรเป็น undefined ทันที ส่วน let/const ถูก hoist แต่อยู่ใน Temporal Dead Zone (TDZ) — การเข้าถึงก่อนบรรทัดที่ประกาศจะ throw ReferenceError
console.log(a); // undefined — var ถูก hoist และ initialisedvar a = 5;
console.log(b); // ReferenceError — let อยู่ใน TDZlet b = 10;Function declarations ก็ถูก hoist อย่างสมบูรณ์ — สามารถเรียกก่อนบรรทัดที่เขียนได้ แต่ Function expressions (ที่กำหนดให้กับตัวแปร) ไม่ถูก hoist
Lexical scope
หัวข้อที่มีชื่อว่า “Lexical scope”Scope ใน JavaScript เป็น lexical (หรือ static): scope ของฟังก์ชันถูกกำหนดโดยตำแหน่งที่เขียนใน source code ไม่ใช่ตำแหน่งที่เรียก ฟังก์ชันด้านในสามารถอ่านตัวแปรจาก scope รอบนอกได้เสมอ
const prefix = 'LOG';
function outer() { const level = 'INFO';
function inner(msg) { // inner อ่านได้ทั้ง prefix (module scope) และ level (outer scope) console.log('[' + prefix + ':' + level + '] ' + msg); }
inner('started');}
outer(); // [LOG:INFO] startedClosures
หัวข้อที่มีชื่อว่า “Closures”Closure คือฟังก์ชันที่เก็บการเข้าถึงตัวแปรของ scope รอบนอกไว้แม้ว่าฟังก์ชันนั้นจะ return ไปแล้ว ฟังก์ชันด้านในไม่ได้คัดลอกค่า — แต่ถือ reference ที่มีชีวิตอยู่ไปยังตัวแปรนั้น
use case คลาสสิกคือ counter factory:
function makeCounter(start) { let count = start;
return { increment: function() { count += 1; }, decrement: function() { count -= 1; }, value: function() { return count; }, };}
const counter = makeCounter(0);counter.increment();counter.increment();counter.increment();counter.decrement();console.log(counter.value()); // 2count อาศัยอยู่ใน scope ของ makeCounter ทุกฟังก์ชันที่ return จาก makeCounter ถือ reference ไปยัง count ตัวเดียวกัน
Demo Closure ที่รันได้
หัวข้อที่มีชื่อว่า “Demo Closure ที่รันได้”สังเกตว่า c1 และ c2 เป็นอิสระจากกันอย่างสมบูรณ์ — การเรียก makeCounter แต่ละครั้งสร้างตัวแปร count ใหม่ใน scope ใหม่ และแต่ละ object ที่ return กลับมาจะ close over ตัวแปรของตัวเอง