這篇文章老闆想跟大家分享物件導向,以及如何使用 js 結合物件導向概念包裝東西,創造並管理物件。
本文翻自 0212 隨意寫程式直播 / 物件導向,若是對文章內容有疑問,或是想要跟著影片一起做,都可以點入觀看影片詳細內容。
工商時間:老闆亂入在這打個小廣告 – 動畫互動網頁特效入門(JS/CANVAS)。網頁技術博大精深,學也學不完,老闆很開心不少同學在上過老師的課程後,成功加入 coder 的一員。 其他平台在對於 js 的課程教學內容上,已經很完善。但這門課不一樣的地方是,老闆會在課程中穿插不同的範例(例如摩斯密碼…等),有興趣的也可以支持一下。
目標
- 物件導向是什麼
- 使用 js 結合物件導向概念包裝東西
為什麼需要物件導向?
這邊我們舉個飛機射擊打怪獸遊戲作為例子。在這個遊戲中,操作者按下射擊會射出子彈,按鍵移動飛機…等功能,在監聽到前述事件後,需要不斷地增加子彈,或是針對飛機進行畫面位置改變,若只是規則簡易的小遊戲,這些都還能硬寫完成。但是當遊戲複雜度增加、要控制的物件增加時,就會讓製作過程非常痛苦。
再舉一個例子,假設今天 人物A 要將 蘋果 交給 人物B 的過程,若是不看畫面,在程式碼內應該要看到三個物件(人物A 、人物B、蘋果),程式碼就會像下面這樣:
A.給(B, 蘋果)
我們把非常繁雜的物件、屬性包成一個抽象的概念,讓他們之間去做互動,管理物件的方式,讓大家在寫程式的時候,不會被底層的內容給干擾。需要使用物件導向的原因,就是因為我們需要一個抽象的介面來處理這些事情。希望能夠透過物件導向,將程式碼簡潔,也不會讓遊戲分數或是各種邏輯條件的程式碼四散各處。
接下來我們來一起寫一些程式,加深對物件導向的理解。
邁向物件導向
要什麼就宣告什麼
我們先來建立一個會打招呼的人,他的名字叫Frank:
var person_name = 'Frank' var personSpeak = function (name) { console.log('Hello, ' + name) } personSpeak(person_name) // Hello, Frank
這樣寫會發現一個狀況,人名和他的動作(Speak)是分開的,這樣對於管理並不友善,所以接下來我們嘗試將人名與說話的方法統一管理。
物件屬性統一管理
接著我們嘗試結合物件,將跟人有相關的屬性(名字、性別、年紀…等)包成物件。這種方式讓函式可以吃到人的所有屬性,也可以針對此來做更多的變化,我們試著把程式碼改動成:
var person = { name: 'Frank', gender: 'Male' } function personSpeak (person) { console.log('Hello, ' + person.name, person.gender) } personSpeak(person) // Hello, Frank, Male
將 function 也放進物件中
但是當程式越來越大,人有太多的屬性,每次呼叫 function 都要傳人這個參數進去,會覺得有點麻煩。這時候,我們開始思考,是不是要連名字也一起放進來,就不用在呼叫 function 的時候,還要傳人的屬性進去,所以我們把程式碼改動成:
var person = { name: 'Frank', gender: 'Male', speak: function () { console.log('Hi, I\'m ' + this.name + '!') } } person.speak() // Hi, I'm Frank!
這個概念可以延伸到我們在寫遊戲,要讓遊戲物件棋子往前,不需要呼叫往前並傳入棋子這個參數,而是針對這個棋子呼叫往前的 function。
類別定義、函數產生器
當只要產生一個人的時候,我們宣告物件很容易,但是當今天要產生五個、十個、甚至一百個的時候,我們不可能打一百個 var person98… var person99… var person100 吧?所以接下來將這種宣告方式改寫成物件產生器。我們將這種物件產生器稱為「類別定義(模板)」。現在不管要幾個人,我們就可以使用這種方式產出無限多人了!
🔔小叮嚀:類別為抽象概念,程式語言的習慣上也會將類別的首字大寫
var Person = function (name, gender) { this.name = name this.gender = gender this.speak = function () { console.log('Hi, I\'m ' + this.name + '!') } } var person1 = new Person ('Frank', 'Male') var person2 = new Person ('Mick', 'Female') person1.speak() // Hi, I'm Frank! person2.speak() // Hi, I'm Mick!
抽出共同方法
現在我們使用 chrome 的開發者工具,檢查 person1, person2 的結構會發現。產生器所產出的每個物件,包進去的函式都是一個新的 function ,並不是共用的。
person1.speak = function () { console.log('Hello, I\'m ' + this.name + '!') } person1.speak() // Hello, I'm Frank! person2.speak() // Hi, I'm Mick!
這時狀況來了,假設今天我們要改變某一個人說話這個函式的內容,就必須一個一個去修改。這樣的操作顯得有點不人性化。我們希望能夠在修改函式的時候,只需修改一次。所以我們可以試著將共同的函式抽出,只要是抽象定義共用的,我們就將它拉出來,而不是再複製一份。
我們針對人這個類別,將共同的函式抽出,改使用 prototype(原型,不會變動的,有點像是人的根源的概念)。
var Person = function (name, gender) { this.name = name this.gender = gender } // 將共同方法抽出 Person.prototype.speak = function () { console.log('Hi, I\'m ' + this.name + '!') } var person1 = new Person ('Frank', 'Male') var person2 = new Person ('Mick', 'Female') person1.speak() // Hi, I'm Frank! person2.speak() // Hi, I'm Mick! // 修改共同方法 Person.prototype.speak = function () { console.log('Hello, I\'m ' + this.name + '!') } //說話從Hi變成了Hello person1.speak() // Hello, I'm Frank! person2.speak() // Hello, I'm Mick!
這時候再使用開發者工具檢查 Person,會發現它的結構變成如下,只有兩個屬性,沒有打招呼的函式,理論上是無法說話,那為什麼 person1 還能說話呢? 發現他們參考到相同的源頭 _proto_
這個函數共用的概念,可以拿來做許多的應用,例如彈珠檯遊戲,每個球都有自己的位置和移動,我們就可以用這個概念去產出許多顆彈珠。
繼承 – 抽取出更抽象的類別
現在的物件產生器,不僅可以幫我們快速產出一個又一個的人,並且擁有一樣的函式。以為這樣就結束了嗎?太小看物件導向了。像是球球對打的遊戲中,球和兩個板子雖然完全不相干,但是我們可以發現,他們除了外型不同之外,其實有許多共同屬性與方法(例如x, y的座標、移動、碰撞),所以我們是不是可以依照需求,抽取出更抽象的類別。
js 內的繼承非常的麻煩,若是想要了解更詳細的內容,也可以參考動畫互動網頁特效入門(JS/CANVAS)課程。在課程內的範例,是以狗去繼承生物體的屬性。狗和人都是生物,都會有名字、生命長度…等基本屬性,但是狗自己又有品種、喜歡吃的東西等屬性和方法,這時候我們就可以執行繼承,讓狗除了自己特有的屬性外,還能夠繼承生物體本身的基本屬性。
剛剛我們已經製作好人的產生器,並且加了賦予他們說話能力的函式,現在我們多了一個新的類別 – 工作者WorkPerson,會有自己額外的屬性(自己的工作)。我們並不會想再把人的屬性寫一遍,額外多一筆工作的值,這種寫法非常地冗,該如何讓 WorkPerson 使用人的屬性呢?首先讓我們整理出這次的三個新名詞:
- 繼承:類別繼承到物件,產生的人都要能有這些方法, WorkPerson 要能回去參考 Person 說話的方法
- prototype:原型、不會動的
- _proto_:共同源頭
操作的步驟如下:
繼承者有原始的屬性可以使用 – call,WorkPerson 初始化時,要使用 Person 的屬性執行
我們把程式碼改寫成如下:
var Person = function (name, gender) { this.name = name this.gender = gender } Person.prototype.speak = function () { console.log('Hi, I\'m ' + this.name + '!') } // 錯誤寫法 var WorkPersonFalse = function (name, gender, work) { this.name = name this.gender = gender this.work = work } // 正確寫法 - 步驟1 var WorkPerson = function (name, gender, work) { Person.call(this, name, gender) } var person = new WorkPerson('Amy', 'Female', 'designer')
這時候我們可以從開發者工具看到 person 已經有 Person 宣告的屬性了。
但此時,我們發現 person 還不能參考到 Person 的方法 speak。所以我們接下來再做些處理
// 步驟 2 var WorkPerson = function (name, gender, work) { Person.call(this, name, gender) this.work = work } // 錯誤寫法 WorkPerson.prototype = Person.prototype // 正確寫法 WorkPerson.prototype = Object.create(Person.prototype) WorkPerson.prototype.speakWork = function () { console.log('I\'m ' + this.work + '.') } var person = new WorkPerson('Amy', 'Female', 'designer')
那為什麼我們不能直接寫成 WorkPerson.prototype = Person.prototype 呢?
因為如果我們今天針對 WorkPerson.prototype.speak 去修改,也會連帶修改到 Person 中的 speak 方法,這並不是我們希望達到的效果,我們希望的只是 WorkPerson 參考 Person 的方法。
當用錯誤方式去宣告,造成 WorkPerson 有一個說自己工作的函式時, Person 也會有這個函式。
複寫
老闆在這邊也給大家一個小提示,今天當 WorkPerson 自己也有 speak 的函式時,它就會優先使用自己有的。當它沒有這個函式時,才會向它繼承的對象去找有沒有這個方法。
Person.prototype.speak = function () { console.log('I\'m ' + this.work + '.') } WorkPerson.prototype.speak = function () { console.log('Aloha.') }
以上為物件導向的過程,大家可以一起跟著寫寫看,透過實際操作了解之中的差別。也因為現在工具越來越方便,比較少會去直接接觸到這些內容,若是大家想要對程式有更深的了解,我們建議大家還是可以去了解一下底層的概念。
在動畫互動網頁特效入門(JS/CANVAS)課程裡面,分成三個章節在講物件導向:概念篇、繼承篇及實作篇,有更詳細的介紹,再完成一個小遊戲。
老闆來閒聊
另外老闆也跳出來和大家分享,在老闆進行網頁設計的過程,常用的一些軟體和工具。
網頁設計
老闆在製作遊戲前,都會先畫個草圖,來敘述遊戲配置和過關條件。製作網頁時,老闆會使用模板工具( Zeplin, Sketch, Illustrator.. )來做內部與外部的設計稿討論、tag 討論。 動畫互動網頁特效入門(JS/CANVAS)會和大家分享一些動態網頁的形式與實際應用,以及怎麼使用 Sketch 以及 Zeplin 來執行專案,有興趣想深入了解老闆怎麼利用這些工具進行設計,一樣可以支持一下老闆的課程。
後端 – firebase
老闆在這邊跟大家誠摯推薦 – firebase 。 firebase 可以做為後端資料庫使用外,也支援檔案上傳,取得 url 就可以直接將圖片引用到你的網站中。作品集若是使用 firebase 串的,若是在後台更改後,可以馬上在前端看到畫面上的變動。也可以利用 firebase 來製作權限功能,提供不同權限的人有不同功能。
案子分享 – 雜學校
這個案子屬於單頁式的 spa ,透過動態轉換,不會有換頁效果。也因為是單頁應用程式,所有狀態是同步的。現在的網站會把整個網站想像成一個應用程式,不像以往是一頁一頁區分的,好處是通用狀態不用存在後端 php session內,也不會有換頁必須要重整頁面的狀況。
這次的分享就到這邊,如果有甚麼問題,都歡迎私訊老闆 來點寇汀吧粉專,或是到我們的Instagram上看看最近都在玩些甚麼吧!
此篇直播筆記由幫手 H 協助整理