iT邦幫忙

2024 iThome 鐵人賽

DAY 16
0

建構子函數 constructor function 是面向對象編程中的一個重要概念,用於創建和初始化對象。

MDN 文件:物件導向程式設計

定義建構子函數及其用途


建構子函數
建構子函數是一種特殊的函數,用於創建和初始化新對象。
在 JavaScript 中,函數本身可以使用函數來定義對象的屬性和方法,所以可以使用它們來創建建構子函數。
建構子函數的命名慣例是使用大寫字母開頭,以便區分它們與普通函數,當使用 new 關鍵字來調用建構子函數時,它會返回一個新對象並初始化它。

  • 特點
    • 用於創建對象 -> 建構子函數用來創建具有指定屬性和方法的新對象。
    • 使用 new 關鍵字 -> 通常建構子函數會與 new 關鍵字一起使用,以便創建新的實例對象。
    • 函數名稱首字母大寫 -> 為了區分建構子函數和普通函數,建構子函數的名稱通常以大寫字母開頭。
    • 初始化屬性 -> 在建構子函數內部,this 代表新創建的對象,可以用 this 來初始化對象的屬性。

定義建構子函數
建構子函數的作用是創建新對象並初始化它們。

/**
 * Phone 是一個建構子函數,用於創建 Phone 類型的對象
 * 
 * @param {*} make 
 * @param {*} model 
 */
function Phone(make, model) {
    this.make = make; // 初始化 make 屬性
    this.model = model; // 初始化 model 屬性
}

/**
 * 為所有 Phone 實例添加方法
 * 
 */
Phone.prototype.start = function () {
    console.log(`${this.make} ${this.model} is starting.`);
};

使用 new 關鍵字來創建新實例


使用 new 關鍵字來調用建構子函數,會進行以下操作:

  1. 創建一個新對象。
  2. 將新對象的 __proto__ 設置為建構子函數的 prototype
  3. this 指向新創建的對象。
  4. 執行建構子函數內的代碼,設置對象的屬性。
  5. 返回新對象(如果建構子函數沒有返回其他對象,則默認返回 this)。
// 創建一個 Phone 的實例
const myPhone = new Phone('Apple', 'iPhone 16 Pro');

// 調用實例方法
myPhone.start(); // Apple iPhone 16 Pro is starting.

🤔 一定要使用 new 嗎?
如果不使用 new 來調用建構子函數,this 會指向全局對象(在瀏覽器中是 window),這可能會導致錯誤,為了避免這個問題,建構子函數應該總是使用 new 來調用。

const hisPhone = Phone('samsung', 'Galaxy Z Fold6');

// undefined,因為 Phone 函數沒有返回值
console.log(hisPhone);

// samsung,因為 window 調用 Phone,this 就是 window 所以 make, model 都掛在上面
console.log(window.make);

建構子函數和 ES6+ 的語法糖 class


在 ES6 JavaScript 引入了 class 語法,這是一種更清晰的語法來定義類和建構子函數,這對於面向對象編程更直觀,但實際上它仍然基於原型繼承和建構子函數。

依照以上的範例,接下來使用 class 來改寫,可以對應著上方的原始碼與下方的一起看,僅有三個小地方要稍作修改:

  1. function Phone 定義名稱 -> class Phone
  2. 初始化物件屬性寫在 constructor ()內,一個 class 也只能有一個 constructor
  3. 原型方法直接寫在 class 內就可以了
class Phone {
    constructor(make, model) {
        this.make = make;
        this.model = model;
    }

    start() {
        console.log(`${this.make} ${this.model} is starting.`);
    }
}

const myPhone = new Phone('Apple', 'iPhone 16 Pro');
myPhone.start();  // Apple iPhone 16 Pro is starting.

常見錯誤


第 4 天:函式 function內容提到提升(hosting),函數因為有提升所以可以先呼叫再寫函數宣告。

console.log(sum(1, 2));    // 3

function sum(x, y) {
  return x + y;
}

但是...函數宣告和類別宣告的一個重要差別在於函數宣告是 hosting ,類別宣告則不是;也就是說要先宣告才能呼叫使用,不然會出現錯誤。
錯誤 vs 正確

classes hosting

資料來源:MDN文件 - Classes

OOPs


👍🏻👍🏻👍🏻 推薦好片:淺談 Javascript 設計模式 | Alex 宅幹嘛

前一天提到繼承,今天要進入的主題開門就提到物件導向,但是什麼是物件導向呢?
今天就用程式碼說明 OOPs 結束這美好的一天吧!

物件導向 Object-Oriented 是一種程式設計的方法論,它將程式中的資料和相關的操作封裝在一起,以形成稱為物件 Object 的實體。

在物件導向程式設計中,一個物件包含了數據(稱為屬性或狀態)和操作這些數據的方法(稱為方法或行為)。

object 物件

回顧:第 5 天:物件與陣列

物件是 OOP 的基本架構,在 JavaScript 中,class 可以被認為是一種特殊類型的函數,使用建構子可以實例化物件。

class 類
類別是創建物件的藍圖,它們封裝了資料和功能。

class Phone {
  constructor(brand, model) {
    this.brand = brand;
    this.model = model;
  }

  start() {
    console.log(`${this.brand} ${this.model} is starting`);
  }
}

const myPhone = new Phone("Apple", "iPhone 16 Pro");
myPhone.start();

Encapsulation 封裝
封裝代表將資料和處理該資料的方法捆綁在單一單元(物件)內,還涉及控制對某些物件組件的存取。

MDN 文件:Public class fieldsPrivate properties

class Car {
  // 私有字段,初始化為 0,ES2020(ES11)引入的一個新特性,用來在類內部隱藏數據,使其無法從類外部直接訪問
  #mileage = 0;

  constructor(brand) {
    // 公有字段,初始化為 brand
    this.brand = brand;
  }

  drive(distance) {
    // 在類內部修改私有字段
    this.#mileage += distance;
  }

  getMileage() {
    // 從類內部讀取私有字段
    return this.#mileage;
  }
}

const myCar = new Car("Toyota");

myCar.drive(150);
console.log('1', myCar.getMileage());
console.log('2', myCar.#mileage);

console

Inheritance 繼承
繼承允許一個類別從另一個類別繼承屬性和方法。

class Machine {
  constructor(brand) {
    // 初始化 brand 屬性
    this.brand = brand;
  }

  start() {
    console.log("The machine is starting");
  }
}

class Car extends Machine {
  constructor(brand, model) {
    // 調用父類 Machine 的建構子
    super(brand);

    // 初始化 model 屬性
    this.model = model;
  }
}

const myCar = new Car("Lexus", "NX");

console.log(myCar.brand);  // Lexus
console.log(myCar.model);  // NX
myCar.start();             // The machine is starting

Polymorphism 多態
多態性指的是同一方法名在不同類別中可以有不同的實現。

/**父類別*/
class Machine {
  makeSound() {
    console.log("Machine sound");
  }
}

/**子類別 - Car*/
class Car extends Machine {
  makeSound() {
    console.log("Honk honk!");
  }
}

/**子類別 - Truck*/
class Truck extends Machine {
  makeSound() {
    console.log("Beep beep!");
  }
}

const machines = [new Car(), new Truck()];

machines.forEach((machine) => machine.makeSound());

console

Abstraction 抽象
抽象化是指只暴露必要的功能接口,而隱藏實現的細節,學後端的歷程中也有遇到這個抽象,真的很抽象。

/** 封裝 */
class Car {
  // 初始化 brand 和 model 屬性
  constructor(brand, model) {
    this.brand = brand;
    this.model = model;
  }

  /** 公共方法,提供了啟動車輛的高級"接口"
   * start() 會負責具體實現細節
   * 車子引擎運作都是內部方法,車主不需要知道也不用理解
   */
  start() {
    this.checkFuel();
    this.pumpFuel();
    this.igniteFuel();

    console.log("Car started!"); // 車輛啟動
  }

  /** 
    * 這些方法是從車主抽象化出來的
    * 屬於內部私有的方法被封裝起來
    */
  checkFuel() {
    /* 檢查燃料的操作,例如:5油3水 */
  }
  pumpFuel() {
    /* 泵送燃料,加油的操作 */
  }
  igniteFuel() {
    /* 點燃燃料的操作 */
  }
}

const myCar = new Car("Lexus", "NX");

myCar.start(); // 車主只需要知道啟動方法

或是用建造一台車子來說明:客戶不需要知道車子怎麼被製造出來的,他只需要從產品目錄表知道有哪些車種,然後就可以下訂單把他開回家!
抽象邏輯說明工廠設計模型


上一篇
第 15 天:JavaScript 的原型鏈與繼承
下一篇
第 17 天:關於 this 這件事
系列文
30天 JavaScript 提升計畫:從零到精通結合2024年的創新功能30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言