โคลเจอร์ใน TypeScript
ในบทความนี้ เราจะอธิบายเกี่ยวกับโคลเจอร์ใน TypeScript
YouTube Video
โคลเจอร์ใน TypeScript
โคลเจอร์คืออะไร?
โคลเจอร์ หมายถึงความสามารถในการเก็บการอ้างอิงถึงขอบเขต (environment) ที่ฟังก์ชันถูกประกาศ แม้ว่าฟังก์ชันนั้นจะถูกเรียกใช้นอกขอบเขตดังกล่าวก็ตาม ด้านล่างนี้จะอธิบายโคลเจอร์รวมถึงการกำหนดประเภท (type annotations)
ง่าย ๆ คือ โคลเจอร์คือการรวมกันระหว่างฟังก์ชันกับสภาพแวดล้อมของตัวแปรที่ฟังก์ชันถูกนิยาม ซึ่งเปิดโอกาสให้ฟังก์ชันสามารถเข้าถึงสภาพแวดล้อมนั้นได้เมื่อถูกเรียกใช้งาน
กลไกพื้นฐานของโคลเจอร์
ใน TypeScript เมื่อมีการประกาศฟังก์ชันภายในฟังก์ชันอีกตัวหนึ่ง จะเห็นได้ว่าฟังก์ชันภายในสามารถเข้าถึงตัวแปรของฟังก์ชันภายนอกได้ ตัวอย่างพื้นฐานของโคลเจอร์พร้อมการกำหนดประเภท (type annotations)
1function outerFunction(): () => void {
2 let outerVariable: string = "I am from outer function";
3
4 function innerFunction(): void {
5 // The inner function accesses the variable of the outer function
6 console.log(outerVariable);
7 }
8
9 return innerFunction;
10}
11
12const closure: () => void = outerFunction();
13closure(); // "I am from outer function"
- ประเภทค่าที่ส่งกลับของ
outerFunction
คือ() => void
ซึ่งหมายความว่าฟังก์ชันนี้จะส่งกลับฟังก์ชันอีกตัวหนึ่ง - ประเภทของ
innerFunction
ถูกกำหนดไว้ชัดเจนว่าเป็นvoid
แสดงว่าไม่มีค่าที่ส่งกลับ
การใช้งานและข้อดีของโคลเจอร์
การห่อหุ้มข้อมูล (Data Encapsulation)
ตัวอย่างโคลเจอร์ที่มีการกำหนดประเภท (type annotations) สำหรับการห่อหุ้มข้อมูล
1function createCounter(): () => number {
2 let count: number = 0;
3
4 return function (): number {
5 count += 1;
6 return count;
7 };
8}
9
10const counter: () => number = createCounter();
11console.log(counter()); // 1
12console.log(counter()); // 2
13console.log(counter()); // 3
- ฟังก์ชัน
createCounter
จะส่งกลับฟังก์ชันที่มีประเภทเป็น() => number
- ตัวแปร
count
ถูกกำหนดประเภทเป็นnumber
และมีการจัดการภายในโคลเจอร์
ฟังก์ชันขั้นสูง (Higher-Order Functions)
โคลเจอร์มีประโยชน์เมื่อสร้างฟังก์ชันขั้นสูง ต่อไปนี้คือตัวอย่างของฟังก์ชันขั้นสูงพร้อมกำหนดประเภทอย่างชัดเจน
1function createMultiplier(multiplier: number): (value: number) => number {
2 return function (value: number): number {
3 return value * multiplier;
4 };
5}
6
7const double: (value: number) => number = createMultiplier(2);
8console.log(double(5)); // 10
9
10const triple: (value: number) => number = createMultiplier(3);
11console.log(triple(5)); // 15
createMultiplier
รับอาร์กิวเมนต์ประเภทnumber
และส่งกลับฟังก์ชันที่มีประเภทเป็น(value: number) => number
- ฟังก์ชันภายในก็รับ
value
เป็นประเภทnumber
และส่งผลลัพธ์กลับเป็นประเภทnumber
เช่นกัน
ตัวอย่างการใช้โคลเจอร์ใน TypeScript
การสร้างตัวนับที่มีขอบเขตแบบโคลเจอร์ พร้อมการกำหนดประเภท (type annotations)
1function rangeCounter(min: number, max: number): () => number | string {
2 let count: number = min;
3
4 return function (): number | string {
5 if (count <= max) {
6 return count++;
7 } else {
8 return `Count has exceeded the maximum value: ${max}`;
9 }
10 };
11}
12
13const counter: () => number | string = rangeCounter(1, 5);
14
15console.log(counter()); // 1
16console.log(counter()); // 2
17console.log(counter()); // 3
18console.log(counter()); // 4
19console.log(counter()); // 5
20console.log(counter()); // "Count has exceeded the maximum value: 5"
- ฟังก์ชัน
rangeCounter
จะส่งกลับฟังก์ชันที่สามารถส่งค่ากลับเป็นnumber
หรือstring
- ในฟังก์ชันภายใน ถ้า
count
เกินค่าmax
จะส่งข้อความประเภทstring
กลับมา มิฉะนั้นจะส่งค่าประเภทnumber
ข้อควรระวังเมื่อใช้โคลเจอร์
ความเสี่ยงเรื่องการรั่วไหลของหน่วยความจำจากโคลเจอร์
โคลเจอร์สามารถเก็บตัวแปรจากขอบเขตภายนอก ซึ่งบางครั้งอาจทำให้เกิดปัญหาการรั่วไหลของหน่วยความจำ โคลเจอร์ที่ไม่จำเป็นควรถูกปล่อยออกจากหน่วยความจำอย่างชัดเจน
1function createLeak(): () => void {
2 // Large array consuming significant memory
3 const largeArray: string[] = new Array(1000000).fill("leak");
4
5 // Closure capturing `largeArray`
6 return function (): void {
7 console.log(largeArray[0]); // Using the captured array
8 };
9}
10
11// Create a closure that holds a reference to the large array
12let leakyFunction = createLeak();
13
14// The large array is not released as `leakyFunction` still references it
15
16// When the object is no longer needed
17leakyFunction = null; // Explicitly remove the reference
- ในโค้ดนี้
largeArray
ที่สร้างขึ้นภายในcreateLeak
ตามปกติควรจะถูกปล่อยเมื่อออกนอกขอบเขต แต่จะไม่ถูกปล่อยเพราะโคลเจอร์ยังอ้างถึงlargeArray
อยู่ ตราบใดที่leakyFunction
ยังมีอยู่ หน่วยความจำที่ไม่จำเป็นนี้ก็จะยังถูกสงวนไว้ - เมื่ออ็อบเจกต์หรือตัวแปรไม่จำเป็นต้องใช้งานแล้ว หากตั้งค่าการอ้างอิงนั้นเป็น
null
ตัวเก็บขยะ (garbage collector) จะสามารถตรวจจับและปล่อยหน่วยความจำนั้นได้
การใช้โคลเจอร์ผิดพลาดในลูป
เมื่อสร้างโคลเจอร์ในลูป อาจเกิดปัญหาในการอ้างอิงตัวแปรเดียวกัน ตัวอย่างต่อไปนี้แสดงกรณีที่ตัวแปร i
ทำงานไม่ถูกต้อง
1for (var i: number = 0; i < 3; i++) {
2 setTimeout((): void => {
3 console.log(i);
4 }, 1000);
5}
6// Output: 3, 3, 3
โค้ดนี้ไม่ได้ผลลัพธ์ที่ต้องการ เพราะ i
อ้างอิงถึงค่าที่เป็น 3 ในตอนจบลูป เพื่อแก้ไขปัญหานี้ สามารถใช้ let
เพื่อแยกขอบเขต หรือใช้ฟังก์ชันที่ถูกเรียกใช้งานทันที (immediately-invoked function)
1for (let i: number = 0; i < 3; i++) {
2 setTimeout((): void => {
3 console.log(i);
4 }, 1000);
5}
6// Output: 0, 1, 2
เมื่อใช้ let
ขอบเขตของตัวแปร i
จะถูกแยกในแต่ละรอบของลูป ทำให้ได้ผลลัพธ์ที่คาดหวัง
สรุป
ใน TypeScript การใช้โคลเจอร์ทำให้โค้ดมีความปลอดภัยและคาดการณ์ได้มากขึ้นด้วยการใช้ระบบประเภท (type system) การใช้โคลเจอร์อย่างเหมาะสม ช่วยให้สามารถห่อหุ้มข้อมูลและออกแบบฟังก์ชันขั้นสูงได้อย่างยืดหยุ่น นอกจากนี้ ควรระวังเรื่องการจัดการหน่วยความจำและการอ้างอิงขอบเขตที่ไม่ตั้งใจเมื่อใช้งานโคลเจอร์
คุณสามารถติดตามบทความข้างต้นโดยใช้ Visual Studio Code บนช่อง YouTube ของเรา กรุณาตรวจสอบช่อง YouTube ด้วย