Back to notes
mastery-frontend-typescript

เจาะลึก Scope, Closures และกลไก Memory ของ JavaScript/TypeScript

เข้าใจเบื้องหลังการทำงานของ Execution Context, Lexical Environment และวิธีที่ V8 Engine จัดการกับ Closures ที่คุณอาจไม่เคยรู้มาก่อน

January 29, 20262 min read readNNexis by Seereen

บทนำ

Scope และ Closure ไม่ใช่แค่กฎการเขียนโค้ด แต่มันคือ กลไกการบริหารจัดการหน่วยความจำ (Memory Management) ของภาษา JavaScript (และ TypeScript) การเข้าใจเรื่องนี้อย่างลึกซึ้งจะทำให้คุณเขียนโค้ดที่มีประสิทธิภาพและหลีกเลี่ยง Memory Leak ได้


1. Execution Context & Call Stack

ทุกครั้งที่ฟังก์ชันถูกเรียก (Invoked) JS Engine จะสร้าง Execution Context ขึ้นมา 2 ส่วน:

  1. Memory Component (Variable Environment): เก็บค่าตัวแปรและฟังก์ชัน
  2. Code Component (Thread of Execution): รันคำสั่งทีละบรรทัด

เมื่อฟังก์ชันทำงานเสร็จ Context จะถูกทำลาย (Popped off stack) และ Memory ก็ควรจะถูกเคลียร์... ยกเว้นกรณีพิเศษที่เรียกว่า Closure


2. Deep Dive into Closures

Closure เกิดขึ้นเมื่อฟังก์ชันลูก (Inner Function) "อ้างอิง" ตัวแปรจากฟังก์ชันแม่ (Outer Function)

HLJS TYPESCRIPT
function outer() {
  let count = 0; // ตัวแปรนี้ควรจะตายเมื่อ outer() จบ

  function inner() {
    count++; // แต่อ้างอิงถึง count
    console.log(count);
  }

  return inner;
}

const myFunc = outer(); // outer จบการทำงานแล้ว
myFunc(); // 1
myFunc(); // 2

เกิดอะไรขึ้นเบื้องหลัง? (Under the hood) ปกติเมื่อ outer() จบ ตัวแปร count ต้องโดน Garbage Collected (GC) ทิ้งไป แต่เนื่องจาก inner ยังถูกส่งออกไปข้างนอก (return) และมันยัง "ผูกพัน" (Bound) อยู่กับ count

JS Engine (เช่น V8) จะฉลาดพอที่จะ ไม่ทำลาย count แต่จะเก็บมันไว้ใน Heap Memory พิเศษที่เรียกว่า "Closure Scope" หรือบางที่เรียก [[Scopes]] property นี่คือสาเหตุที่ Closure กิน Memory มากกว่าฟังก์ชันปกติ


3. Lexical Scope vs Dynamic Scope

JavaScript ใช้ Lexical Scope (Static Scope) แปลว่า: "ตำแหน่งที่คุณวางโค้ด คือสิ่งที่กำหนด Scope" ไม่ใช่ตำแหน่งที่คุณเรียกใช้ฟังก์ชัน

HLJS TYPESCRIPT
let name = "Global";

function sayName() {
  console.log(name);
}

function dummy() {
  let name = "Local";
  sayName(); // เรียก sayName ที่นี่
}

dummy();
// Output: "Global" (ไม่ใช่ Local!)

ทำไม? เพราะตอน sayName ถูกประกาศ (Defined) มันอยู่ข้างนอก มันจึงจำ Scope ข้างนอก (Lexical Environment) ติดตัวไปตลอดกาล ไม่ว่าใครจะเป็นคนเรียกมันก็ตาม


4. let, const และ Block Scope (The TDZ)

ทำไม var ถึงแย่? เพราะ var ผูกกับ Function Scope เท่านั้น ไม่สนใจ { }

HLJS TYPESCRIPT
if (true) {
  var x = 10;
}
console.log(x); // 10 (ทะลุออกมาได้เฉย)

ใน TypeScript/Modern JS เราใช้ let/const ซึ่งทำงานในระดับ Block Scope เบื้องหลังคือ JS Engine จะสร้าง Zone พิเศษที่เรียกว่า Temporal Dead Zone (TDZ) ตั้งแต่เริ่ม Block จนถึงบรรทัดที่ประกาศตัวแปร ถ้าพยายามเรียกใช้ก่อนหน้านั้น Engine จะปา Error ยับยั้งทันที


สรุป

  • Closure คือการที่ฟังก์ชัน "หอบหิ้ว" ข้อมูล (Backpack) ของ Scope แม่ติดตัวไปด้วย
  • ข้อมูลใน Closure จะไม่ถูก Garbage Collected ตราบใดที่ฟังก์ชันนั้นยังถูกใช้งาน
  • Lexical Scope คือการดูตำแหน่งการเขียนโค้ด ไม่ใช่ตำแหน่งการเรียก
  • การใช้ let/const ช่วยจัดการ Scope ให้แคบลงและปลอดภัยขึ้น ลดความเสี่ยง Memory Leak จาก Global variables
Share this note

© 2026 My Notes by Seereen