Object Oriented Programming(OOP)是什麼?
OOP又被叫做物件導向程式設計,是一種程式設計法(Programming paradigm)。OOP與傳統程式設計:程序式程式設計(Procedural programming)有著很大的不同,OOP將相關的變數(屬性)與函式(方法)結合成一個單位(也就是物件),這使得程式的複雜性降低、靈活性提高。
OOP的四大pillar(支柱)
OOP總共有四大支柱,分別是:
- Encapsulation
- Abstraction
- Inheritance
- Polymorphism
Encapsulation(封裝)
Encapsulation這個概念是在說,我們可以把許多屬性、方法包裝成一個物件使用把OOP與Procedural programming來比較就會像是這樣:
//Procedural programming 變數與函式分開寫:
let income = 50000
let expense = 30000
function saving(income, expense) {
return income - expense
}
saving(); //20000
//Object Oriented Programming 將變數與函式結合成一個物件
let person = {
income: 50000,
expense: 30000,
saving: function() {
return (this.income - this.expense)
}
}
person.saving(); //20000
Encapsulation的優點在於參數很少或沒有,降低了程式的複雜性與提供了靈活性。
Abstraction(抽象)
Abstraction指得是將此物件的某些屬性與方法隱藏(hide)起來。舉例來說,我們按下遙控器的按鈕就能轉台,其實這是靠遙控器內的小零件互相作用的,但我們不必知道這些。這些被隱藏在遙控器裡的零件與作用就像是被隱藏起來的屬性與方法。
Abstraction的優點是①使物件的介面更簡單②減少改變的影響
Inheritance(繼承)
Inheritance的概念就是繼承者擁有某些屬性或是方法,而這些是來自於被繼承者的,使繼承者透過Inheritance可以直接取用這些屬性與方法。
//arr1與arr2都繼承了Array.prototype的屬性與方法
//像是它們都可以使用map()、pop()、push()、reduce()這些方法
arr1 = [1,2,3]
arr2 = [2,3,4]
Polymorphism(多型)
Polymorphism指的是使用相同名稱的方法,傳入不同的參數,會執行不同的指令。
假如我們要讓好多個物件渲染(render)頁面,在Procedural programming就會長得像這樣,我們必須用好多switch和case:
switch(...){
case 'print': renderPrint();
case 'text' : renderText()}
.
.
.}
但當我們用OOP的Polymorphism概念,就會變成這樣:
element.rendar()
什麼是構造函式(Constructor Function)?
其實在筆記8有提到這個,構造函式也是函式的一種,只是它比較特別可以作為模板來創立其他物件們。
定義構造函式
我們可以使用new
關鍵字呼叫構造函式,而透過this
可以幫我們建立新物件。此外,構造函式應該要是大寫字母開頭。
function Movies(title, year) {
this.title = title;
this.year = year;
}
let movie = new Movies('Joker', 2019);
movie; //Movies {title: "Joker", year: 2019}
movie.title; //"Joker"
movie.year; //2019
什麼是Constructor與Class?
前面提到我們要構造一個新物件,它可能會長這樣:
function PCGame(name, genre){
this.name = name;
this.genre = genre;
this.score = null;};
pcGame.prototype.rate = function(score){
this.score = score;
}
let game1 = new PCGame('Age of Empires 3', 'Real-time strategy');
let game2 = new PCGame('The Outer Worlds', 'Action role-playing');
但當我們使用class
與constructor
改寫,它會長得像這樣:
class PCGame {
constructor(name, genre, score) {
this.name = name;
this.genre = genre;
this.socre = score;
}
rate() {
this.score = score
}
}
let game1 = new PCGame('Age of Empires 3', 'Real-time strategy');
let game2 = new PCGame('The Outer Worlds', 'Action role-playing');
class可以說是包裝好的prototype,讓語法看起來更清楚明瞭。
extends與super
- extends:用於創建一個類別(class),此類別是另一個類別的子類別,具有語法繼承
- super:讀取或呼叫此類別的父類別的函數,注意
super
只能在constructor中執行,且super
必須出現在this之前。
按照一般的構造函式寫法,例子會像這樣:
function Pet(name, gender, age) {
this.name = name;
this.gender = gender;
this.age = age;
this.callPet = function() {
console.log(`This ${this.name} is ${this.age} years old, and its gender is ${this.gender}`)
}
}
function Cat(name, gender, age, breed){
Pet.call(this, name, gender, age);
this.breed = breed;
this.callCat = function(){
console.log(`This ${this.name} is the ${this.breed}. It's ${this.age} years old, and its gender is ${this.gender}`)
}
}
cat.callPet();
//This cat is 3 years old, and its gender is female
cat.callCat();
//This cat is the American Shorthair Cat. It's 3 years old, and its gender is female
想要讓Pet()
裡的name
也可以用到Cat()
裡,就必須透過call(this.name)
取得,但extends
和super
可以幫我們少掉這些麻煩!讓我們改寫一下程式:
class Pet {
constructor(name, gender, age) {
this.name = name;
this.gender = gender;
this.age = age;
}
callPet() {
console.log(`This ${this.name} is ${this.age} years old, and its gender is ${this.gender}`)
}
}
class Cat extends Pet { //// 用extends指定父類別
constructor(name, gender, age, breed) {
super(name, gender, age); //用super呼叫父類別的函數
this.breed = breed;
}
callCat(){
console.log(`This ${this.name} is the ${this.breed}. It's ${this.age} years old, and its gender is ${this.gender}`)
}
}
let cat = new Cat('cat', 'female', 3, 'American Shorthair Cat')
cat.callPet();
//This cat is 3 years old, and its gender is female
cat.callCat();
//This cat is the American Shorthair Cat. It's 3 years old, and its gender is female
Static(靜態方法)
static表示類別的靜態方法,被定義為靜態方法的函式可以直接以constructor function呼叫,它也無法被已實體化(new過)的類別物件呼叫。
//Static Method 不需實體化所需類別的實例就可以被呼叫
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
static student(name, age) {
console.log(`I'm ${name}. ${age} years old.`) //不要加this
}
}
Person.student('Teagan', '22')
// I'm Teagan. 22 years old.
//被定義為靜態方法的函式,無法被已實體化(new過)的類別物件呼叫
let person = new Person('Teagan', 22);
person.student; //Uncaught TypeError: person.student is not a function
按照一般寫法class寫法是:
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
student(name, age) {
console.log(`I'm ${this.name}. ${this.age} years old.`)
}
}
let person = new Person('Teagan', 22);
person.student();
// I'm Teagan. 22 years old.
//用平常class的寫法,不實體化所需類別的實例就不能被呼叫!
Person.student('Teagan', '22')
//Uncaught TypeError: Person.student is not a function
Getter與Setter
- get():get語法會將物件屬性綁定到在查找該屬性時將呼叫的函式。
- set():set語法將物件屬性綁定到要呼叫的函式。
- 只有遇到私有變數( _ )時,才需用Getter與Setter取得或設定值。需注意Getter沒有參數傳入,Setter只有一個參數:
class Person {
constructor(name) {
this.name = name;
}
get age() {
if (this._age !== undefined) {
return (`${this.name}'s age is ${this._age}`)
} else {
return ('Valid age!')
}
}
set age(age) {
this._age = age;
}
}
let person = new Person('Teagan')
person.age = 22; //賦值
console.log(person.age); //"Teagan's age is 22"
參考資料: