在前一篇,我們提到「閉包」能讓函式記住自己的環境,
透過作用域封裝資料,避免外部直接修改。
這種 封裝(Encapsulation)的特性,
其實也是「物件導向程式設計」的重要核心。
JavaScript 並不是傳統的物件導向語言,
但它同樣能用不同方式達到「物件導向」的思考模式。
今天就來看看,所謂「物件導向」到底是什麼。
「物件導向(Object-Oriented Programming,簡稱 OOP)」
是一種以物件為中心的設計思維。
它把程式中要處理的事物,想像成一個個「物件」:
每個物件都有「屬性(描述資料)」與「方法(行為)」。
const dog = {
name: "Milo",
bark: function() {
console.log("汪汪!");
}
};
dog.bark(); // 汪汪!
這就是一個最基本的「物件導向思維」
封裝指的是把資料與方法包在一起,
外部只能透過特定介面(方法)來操作資料。
閉包其實就能模擬這種特性:
function createUser(name) {
let _name = name; // 私有變數
return {
getName() {
return _name;
},
setName(newName) {
_name = newName;
}
};
}
const user = createUser("Ken");
console.log(user.getName()); // Ken
user.setName("Kenneth");
console.log(user.getName()); // Kenneth
_name
無法被外部直接修改,getName
或 setName
才能存取。繼承讓新物件能延續現有物件的特性,
不需要重複撰寫相同的邏輯。
在 JavaScript 裡,繼承是透過 原型(Prototype) 機制實現的。
function Animal(name) {
this.name = name;
}
Animal.prototype.sayName = function() {
console.log("My name is " + this.name);
};
function Dog(name) {
Animal.call(this, name); // 繼承屬性
}
Dog.prototype = Object.create(Animal.prototype); // 繼承方法
Dog.prototype.constructor = Dog;
const dog = new Dog("Milo");
dog.sayName(); // My name is Milo
多型的意思是:不同物件可以有相同的方法名稱,
但根據物件的不同,執行的結果會不同。
function Animal() {}
Animal.prototype.speak = function() {
console.log("Animal sound");
};
function Dog() {}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.speak = function() {
console.log("汪汪!");
};
function Cat() {}
Cat.prototype = Object.create(Animal.prototype);
Cat.prototype.speak = function() {
console.log("喵~");
};
const animals = [new Dog(), new Cat()];
animals.forEach(a => a.speak());
// 汪汪!
// 喵~
在 ES6 之後,JavaScript 引入了更直覺的 class 語法,
用來包裝原本的 prototype 寫法,讓語意更清楚。
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} 發出聲音`);
}
}
class Dog extends Animal {
speak() {
console.log(`${this.name} 汪汪叫`);
}
}
const dog = new Dog("Milo");
dog.speak(); // Milo 汪汪叫