Closure เป็นคำสำคัญที่สุดใน JavaScript เป็นคำถามสัมภาษณ์งานที่ FAANG companies บ่อยมาก บทความนี้จะอธิบาย closure และ scope พร้อมตัวอย่างง่ายๆ และคำถามสัมภาษณ์ตัวอย่าง
Scope (ขอบเขต)
เมื่อพูดว่า "ในขอบเขต" หรือ "นอกขอบเขต" ของโปรเจกต์ หมายถึงอะไร?
คิดถึงกล้องโทรทะเล (telescope) หรือกล้องสิบปืน (periscope) เครื่องมือเหล่านี้แสดงสิ่งต่างๆ ภายในเลนส์: อยู่ ในขอบเขต (in scope) หากอยู่นอกเลนส์ จะมองไม่เห็น แสงก็ไม่สามารถส่องผ่านได้ คิดในลักษณะนี้ขณะอ่านเกี่ยวกับ 3 ประเภท scope สำคัญใน JavaScript: Local, Global และ Lexical
Local Scope (ขอบเขตท้องถิ่น)
Local scope คือ scope ที่เล็กที่สุด เมื่อประกาศฟังก์ชัน ทุกอย่างภายในวงเล็บปีกกา {} ถือเป็น local ของฟังก์ชันนั้น ตัวแปรถูกสร้างเมื่อฟังก์ชันถูกเรียก และทำลายเมื่อฟังก์ชันสิ้นสุด
function greeting() {
var websiteName = 'Career Karma';
return `Hello ${websiteName}`;
}
console.log(greeting()); // Hello Career Karma
console.log(websiteName); // ReferenceError: websiteName is not defined
เมื่อ console.log() ผลลัพธ์ของฟังก์ชัน เราเข้าถึง websiteName ได้ แต่นอกฟังก์ชันเข้าถึงไม่ได้ เพราะตัวแปรถูกทำลายเมื่อฟังก์ชันจบ
Global Scope (ขอบเขตทั่วไป)
Global scope คือตัวแปรที่ประกาศนอกฟังก์ชัน สคริปทุกตัว ฟังก์ชันทุกตัว และเมธอดทุกตัวเข้าถึงได้
let counter = 0; // global
const add = () => {
let counter = 0; // local
counter += 1;
return counter;
}
add(); add(); add();
console.log(counter); // 0 หรือ 3? ทำไม?
console.log(counter) จะได้ 0 เพราะ:
- Global counter = 0
- ฟังก์ชัน add มี local counter ของตัวเอง
- ทุกครั้งที่เรียก add() local counter เริ่มที่ 0 ใหม่ เพิ่มเป็น 1 คืนค่า 1 แล้วทำลาย
- Global counter ไม่มีการเปลี่ยนแปลงเลย
Lexical Scope (ขอบเขตเชิงลัทธิ)
Lexical scope คือแนวคิดพื้นฐานสำคัญที่สุดใน JavaScript คือ การสร้างฟังก์ชันหรือตัวแปรจะเข้าถึงได้ในบางส่วนของโค้ด และเข้าถึงไม่ได้ในส่วนอื่น ขึ้นอยู่กับตำแหน่งที่ประกาศ
const init = () => {
const var1 = 'Career'; // outer scope
const second = () => {
const var2 = 'Karma'; // inner scope
console.log(var1); // Career
console.log(var2); // Karma
return var1 + " " + var2;
};
return second();
};
init();
ฟังก์ชัน init() รู้จัก var1 และ second() แต่ไม่รู้จักสิ่งภายใน second() (inner scope) แต่ second() เข้าถึง var1 ของ outer scope ได้
เหมือนตุ๊กตา Matryoshka (ตุ๊กตาซ้อนใน) ตุ๊กตาใหญ่รู้แค่ว่ามีตุ๊กตาย่อยอยู่ในตัว แต่ไม่รู้รายละเอียดภายใน
กฎสำคัญ 2 ข้อ:
- Outer scope เข้าถึง inner scope ไม่ได้
- Inner scope เข้าถึง outer scope ได้
นี่คือ one-way relationship เรียกว่า Lexical Scope
Closure
Closure คล้าย lexical scope แต่ต่างตรงที่ Closure เป็น higher-order function (คืนค่าฟังก์ชัน หรือรับฟังก์ชันเป็นพารามิเตอร์)
Closure คือฟังก์ชันที่เข้าถึง lexical scope ของมันได้ แม้ว่าจะถูกเรียกใช้ภายหลัง (หลังจากฟังก์ชัน parent จบการทำงานแล้ว)
function greeting() {
const userName = "CrrKrma1952";
function welcomeGreeting() {
console.log("Hello, " + userName);
return "Hello, " + userName;
}
return welcomeGreeting; // คืนค่าฟังก์ชัน -> HOF
}
const greetUser = greeting();
greetUser(); // Hello, CrrKrma1952
แม้ greeting() จบการทำงานแล้ว welcomeGreeting() ยังเข้าถึง userName ได้ เพราะมัน "ปิดกวด" (close over) ตัวแปรของ parent ไว้ นี่คือ closure
สรุป
บทความนี้ครอบคลุม Scope (Local, Global, Lexical) และ Closure แนะนำอ่านบทความอื่นๆ เพิ่มเติม เพราะมีมุมมองสอนหลายแบบ โชคดีนะครับ!