โคลเจอร์ใน TypeScript

โคลเจอร์ใน 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 ด้วย

YouTube Video