OOP, Prototype & Module

ES6 เพิ่งเพิ่มคีย์เวิร์ด class เข้ามา — เบื้องหลังจริง ๆ JS ยังทำงานด้วย prototype อยู่ ซึ่งเป็นระบบ OOP ที่ต่างจาก Java

class & constructor

class Car {
    constructor(name, year) {
        this.name = name;
        this.year = year;
    }
}

const myCar1 = new Car("Ford", 2014);
const myCar2 = new Car("Audi", 2019);
ใน JS ใช้คีย์เวิร์ด constructor ตรง ๆ ไม่ต้องตั้งชื่อเหมือนคลาสแบบ Java

method & parameter

class Car {
    constructor(name, year) {
        this.name = name;
        this.year = year;
    }
    age(x) {
        return x - this.year;
    }
}

const myCar = new Car("Ford", 2014);
myCar.age(2026);        // 12

extends & super

function CarFunc(name) {
    this.name = name;
}

class CarClass extends CarFunc {
    constructor(name, brand) {
        super(name);            // เรียก constructor ของแม่ก่อน
        this.carName = brand;
    }
}
ต้องเรียก super() ก่อนใช้ this ในคลาสลูกเสมอ

Encapsulation ด้วย #

class BankAccount {
    #balance = 0;                       // private field

    deposit(amount) {
        this.#balance = this.#balance + amount;
    }
    withdraw(amount) {
        if (amount <= this.#balance) {
            this.#balance = this.#balance - amount;
        } else {
            console.log("Insufficient funds");
        }
    }
    getBalance() {
        return this.#balance;
    }
}

const account = new BankAccount();
account.deposit(1000);
account.withdraw(500);
console.log(account.getBalance());      // 500
// console.log(account.#balance);      // Error — เข้าถึงจากนอกคลาสไม่ได้

JS ไม่มี method overloading

Java ตั้งชื่อเมธอดซ้ำได้ถ้าพารามิเตอร์ต่างกัน แต่ JS ทำไม่ได้ — ใช้ ad-hoc overloading แทน

function foo(a, b) {
    switch (arguments.length) {
        case 0:  // ไม่มี argument
            break;
        case 1:  // ทำงานกับ a
            break;
        case 2:
        default: // ทำงานกับ a และ b
            break;
    }
}

// หรือเก็บฟังก์ชันไว้ใน array ตามจำนวน argument
fooArr = [
    function () {},
    function (a) {},
    function (a, b) {}
];
function foo(a, b) {
    return fooArr[arguments.length](a, b);
}

JS ไม่มี generic

เพราะตัวแปรเป็น dynamic type ไม่ต้องประกาศชนิด ถ้าต้องการ <T> จริง ๆ ต้องไปใช้ TypeScript

Prototype & prototype chain

ก่อนมี class ใน ES6 การสร้าง object ใน JS ใช้ function constructor + prototype

function Car(name) {        // function constructor
    this.name = name;
}
let Car1 = new Car('John');

console.log(Car1.__proto__ === Car.prototype);   // true
Car1 { name: 'John' } __proto__ Car.prototype constructor + property ที่เพิ่ม Object.prototype .toString() ฯลฯ ทุก object ไล่ chain ขึ้นไปจบที่ Object.prototype เสมอ หา property ที่ตัวเองไม่มี → ไล่ขึ้นไปตาม chain → ถ้าไม่เจอเลยได้ undefined

Object.prototype อยู่บนสุดของห่วงโซ่การสืบทอด

เพิ่ม property ทีหลัง — ใช้ .prototype หรือไม่ใช้?

function CarFunc(name) { this.name = name; }
class CarClass extends CarFunc {
    constructor(name, brand) { super(name); this.carName = brand; }
}
let Car2 = new CarClass('John', 'toyota');

// กรณีที่ 1 — ใช้ .prototype
CarFunc.prototype.color = "green";
console.log(Car2.color);      // "green" ← ไหลตาม chain ลงมาถึงลูก

// กรณีที่ 2 — ไม่ใช้ .prototype
CarFunc.color = "green";
console.log(Car2.color);      // undefined ← ไม่ได้อยู่ใน chain
สรุป: ถ้าอยากให้ object ที่สร้างจากคลาสนั้น (รวมถึงคลาสลูก) เห็น property ที่เพิ่มทีหลัง ต้องเพิ่มผ่าน .prototype เท่านั้น

class กับ JSON

class MyClass {
    constructor() { this.foo = 3; }
}
const myClass = new MyClass();
console.log(JSON.stringify(myClass));   // {"foo":3}

let myjson = { foo: 3 };
console.log(JSON.stringify(myjson));    // {"foo":3}  ← เหมือนกัน
object ที่สร้างจาก class กับ object ธรรมดา เมื่อ stringify แล้วได้ผลเหมือนกัน — เพราะสุดท้ายมันก็คือ JavaScript Object ทั้งคู่ (function จะหายไปตอน stringify)

JS Module

แบ่งโค้ดออกเป็นไฟล์ย่อยแล้วเรียกใช้ข้ามไฟล์

1. export ตัวแปรหลายตัว

// person.js
const name = "Jesse";
const age = 40;
export { name, age };

// ไฟล์ที่เรียกใช้
<script type="module">
    import { name, age } from "./person.js";
    console.log(name);
</script>

2. export default (function module)

// person.js
const person = () => {
    const name = "Jesse";
    const age = 40;
    return name + ' is ' + age + ' years old.';
};
export default person;

// ไฟล์ที่เรียกใช้
<script type="module">
    import person from "./person.js";
    console.log(person());
</script>

3. class module

// person.js
class person {
    constructor() {}
    helloworld() { console.log('hello world'); }
}
export default person;
named exportdefault export
เขียน exportexport { name, age };export default person;
เขียน importimport { name } from './person.js'ต้องใส่ปีกกาและชื่อตรงimport person from './person.js'ตั้งชื่ออะไรก็ได้
มีได้กี่ตัวต่อไฟล์หลายตัว1 ตัว
แท็ก script ที่ import module ต้องใส่ type="module" เสมอ
React ยุคใหม่นิยมใช้ function module เป็นหลักมากกว่า class module เพื่อลดปัญหาการจัดการ state