iT邦幫忙

2024 iThome 鐵人賽

DAY 20
0
JavaScript

入門JavaScript系列 第 20

物件導向

  • 分享至 

  • xImage
  •  

在 JavaScript 中,物件導向(OOP,Object-Oriented Programming)是一種強大的編程方式,透過物件(objects)和類別(classes)的概念來組織和管理程式碼。JavaScript 本身不是純粹的物件導向語言,但透過 classprototype 等特性,我們能夠實現物件導向的編程風格。

1. 類別(Class)與建構函式(Constructor)

在 ES6 之前,JavaScript 是基於 prototype 繼承的,但 ES6 引入了 class 語法,使得物件導向的寫法更加簡潔直觀。

建構函式與類別的基本用法:

class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  greet() {
    return `Hello, my name is ${this.name} and I am ${this.age} years old.`;
  }
}

const alice = new Person('Alice', 30);
console.log(alice.greet()); // Hello, my name is Alice and I am 30 years old.

constructor 是用來初始化物件的函式,會在每次使用 new 關鍵字建立物件時自動執行。

2. 繼承(Inheritance)

繼承允許一個類別(子類,subclass)從另一個類別(父類,superclass)繼承屬性和方法,使得程式碼更加可重用和可擴展。

繼承範例:

class Animal {
  constructor(name) {
    this.name = name;
  }

  speak() {
    return `${this.name} makes a sound.`;
  }
}

class Dog extends Animal {
  constructor(name, breed) {
    super(name);  // 呼叫父類的 constructor
    this.breed = breed;
  }

  speak() {
    return `${this.name} the ${this.breed} barks.`;
  }
}

const dog = new Dog('Rex', 'Labrador');
console.log(dog.speak()); // Rex the Labrador barks.

這裡 Dog 類別繼承了 Animal,並且使用 super() 來呼叫父類的 constructorspeak() 方法被覆蓋(重寫),實現了不同的行為。

3. 多型(Polymorphism)

多型允許子類別在不改變接口的前提下,覆寫父類別的方法,讓子類根據需要實現不同的行為。這提升了程式的靈活性。

範例:

class Bird extends Animal {
  speak() {
    return `${this.name} chirps.`;
  }
}

const bird = new Bird('Tweety');
console.log(bird.speak()); // Tweety chirps.

雖然 BirdDog 都繼承自 Animal,但它們的 speak() 方法行為不同,這就是多型的體現。

4. 封裝(Encapsulation)

封裝是指將資料與操作這些資料的方法封裝在一起,並隱藏內部的實現細節。JavaScript 並不具有傳統的私有屬性,但可以透過一些技巧來模擬私有性。

使用命名慣例來模擬私有屬性:

class Person {
  constructor(name) {
    this._name = name; // 以 "_" 開頭表示私有
  }

  getName() {
    return this._name;
  }

  setName(name) {
    this._name = name;
  }
}

const person = new Person('Alice');
console.log(person.getName()); // Alice
person.setName('Bob');
console.log(person.getName()); // Bob

使用 ES6 的 # 符號實現真正的私有屬性:

class Person {
  #age = 0;  // 私有屬性

  constructor(name, age) {
    this.name = name;
    this.#age = age;
  }

  getAge() {
    return this.#age;
  }

  setAge(age) {
    if (age > 0) {
      this.#age = age;
    }
  }
}

const person = new Person('Alice', 30);
console.log(person.getAge()); // 30
person.setAge(35);
console.log(person.getAge()); // 35
// console.log(person.#age); // Error: 私有屬性不能被外部訪問

在這裡,使用 # 符號可以定義真正的私有屬性,無法從外部直接訪問或修改。

5. 靜態方法與屬性(Static Methods and Properties)

靜態方法和屬性是屬於類別本身而非實例的,這意味著它們只能被類別直接呼叫,而不是通過類的實例來呼叫。

範例:

class MathUtil {
  static add(a, b) {
    return a + b;
  }

  static PI = 3.14159;
}

console.log(MathUtil.add(2, 3)); // 5
console.log(MathUtil.PI); // 3.14159

靜態方法 add() 和靜態屬性 PI 只能透過 MathUtil 類別直接訪問,而無法透過實例使用。

6. 抽象類別與介面(Abstract Classes and Interfaces)

JavaScript 本身不直接支持抽象類別與介面,但我們可以透過模式來實現類似的功能。抽象類別是不能被實例化的類,它僅作為其他類別的基礎;介面則是用來定義必須被實現的方法。

模擬抽象類別:

class Shape {
  constructor() {
    if (new.target === Shape) {
      throw new Error("Cannot instantiate abstract class Shape");
    }
  }

  area() {
    throw new Error("Method 'area()' must be implemented.");
  }
}

class Circle extends Shape {
  constructor(radius) {
    super();
    this.radius = radius;
  }

  area() {
    return Math.PI * this.radius ** 2;
  }
}

const circle = new Circle(5);
console.log(circle.area()); // 78.53981633974483
// const shape = new Shape(); // Error: Cannot instantiate abstract class Shape

這裡 Shape 是一個模擬的抽象類別,不能直接實例化,並且要求子類實現 area() 方法。

7. this 關鍵字與上下文綁定

在 JavaScript 中,this 的值根據函式的呼叫方式而變動。了解 this 綁定的方式(如 bind()call()apply())是 OOP 中的重要技巧。

this 的基本綁定方式:

class Person {
  constructor(name) {
    this.name = name;
  }

  greet() {
    console.log(`Hello, I am ${this.name}`);
  }
}

const alice = new Person('Alice');
alice.greet(); // Hello, I am Alice

const greetFunction = alice.greet;
greetFunction(); // Error: 'this' is undefined or doesn't refer to 'Person'

// 使用 bind() 明確綁定 this
const boundGreet = alice.greet.bind(alice);
boundGreet(); // Hello, I am Alice

bind() 可以將 this 明確綁定到特定物件上,避免因為上下文變化導致 this 指向錯誤。

8. 原型鏈(Prototype Chain)與繼承

JavaScript 的物件繼承是基於原型的,每個物件都有一個隱式的 __proto__ 屬性指向它的原型。理解原型鏈有助於掌握物件屬性和方法的查找機制。

原型繼承範例:

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

Person.prototype.greet = function() {
  return `Hello, I am ${this.name}`;
};

const bob = new Person('Bob');
console.log(bob.greet()); // Hello, I am Bob
console.log(bob.__proto__ === Person.prototype); // true

在這裡,bob 繼承了 Person.prototype 上的 greet 方法。當呼叫 bob.greet() 時,JavaScript 會沿著原型鏈查找 greet 方法。


上一篇
函式應用
下一篇
物件導向應用
系列文
入門JavaScript26
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言