以前學的做法。
簡述
我們先來看結果會長什麼樣,等等再來解釋:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| function Person(name) { this._name = name } Person.prototype.getName = function () { console.log(this._name) } Person.prototype.setName = function (newName) { this._name = newName }
const person1 = new Person('PeaNu') const person2 = new Person('PPB')
person1.getName() person2.getName()
|
順便幫你複習一下 ES6 的寫法,做個對照:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| class Person { constructor(name) { this._name = name } getName() { console.log(this._name) } setName(newName) { this._name = newName } }
const person1 = new Person('PeaNu') const person2 = new Person('PPB')
person1.getName() person2.getName()
|
關於 constructor
可以發現 ES5 寫起來沒有那麼直覺,在沒有 class
時只能把 function 當作 constructor 來用,而且還蹦出一堆 prototype
的東西。
另外為了避免跟普通的 function 搞混,一般會像 class 一樣用「大寫開頭」來區分。再來是很重要的一點,就是 constructor 一定要搭配 new
來使用,不然會沒有作用。
所以說只要你看到 new xxx()
的話一定代表是 call 某個 constructor,而不是 function。
prototype 幹嘛用的?
以前我對變數的概念不熟,所以一直不懂 prototype 的實際意義是什麼。但現在熟了以後就很清楚了。
你先想想看,如果不用 prototype 的話會怎樣?
1 2 3 4 5 6 7 8 9 10 11 12
| function Person(name) { this._name = name this.getName = function () { console.log(this._name) } this.setName = function (newName) { this._name = newName } }
const person1 = new Person('PeaNu') const person2 = new Person('PPB')
|
看起來好像沒什麼差?但如果執行這段的話會發現:
1
| console.log(person1.getName === person2.getName)
|
結果是 false
,代表這兩個 getName
是兩個不同的 function。不太懂的話再舉個例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| function getName1() { console.log('hi') } function getName2() { console.log('hi') }
const getNameByReference = getName1
getName1() getName2() getNameByReference()
console.log(getName1 === getName2) console.log(getNameByReference === getName1)
|
這三個 function 都可以正常執行,可是差在哪裡?差在 getName1
和 getName2
是不同的 function,而 getNameByReference
是透過 refer 的方式參考到 getName1
,所以 getNameByReference === getName1
才會是true
。
回到 prototype 的例子也一樣,如果沒有用 prototype 來做設定的話,每當 new 一個 instance 的時候就會重新宣告一個新的 function,所以比對的結果會是 false
,因為它們是不一樣的。
問題很明顯,這樣子很浪費資源,明明每個 function 要做的事情一樣,為什麼不讓它們共用就好?還要幫每一個 instance 都重新宣告一次。
所以 prototype 就誕生了,讓每個 instance 共用同一個 function,就是它的初衷。
至於別人常說 class
是語法糖的原因是因為寫起來比較簡單和直覺,你可以滑上去對比 ES5 和 ES6,就能看到這兩個差別:
- 直接在
class
裡用 constructor,而不是透過 function declaration
- 不需要透過 prototype 來綁定,也能達到一樣的效果
所以現在要實作物件導向都會透過 class
,比較少在用 prototype,但還是要理解它們背後的涵義。
繼承
先來一段範例,複習一下在 ES6 裡面我們是怎麼用 class
來做繼承的。
附註:Admin
會繼承 User
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| class User { constructor(name, email) { this.name = name this.email = email } login() { console.log(`${this.name} has logged in.`) } }
class Admin extends User { constructor(permission, ...props) { super(...props) this.permission = permission this.users = [] } addUser(user) { this.users = this.users.map((user) => ({ ...user })).concat(user) } deleteUser(user) { this.users = this.users.filter((u) => u.name !== user.name) } }
const user1 = new User('peanu', 'peanu@peanu.dev') const user2 = new User('ppb', 'ppb@peanu.dev') const admin = new Admin('root', 'admin', 'admin@peanu.dev')
admin.login() admin.addUser(user1) admin.addUser(user2) console.log('init users', admin.users) admin.deleteUser(admin.users[0]) console.log('deleted users', admin.users)
|
簡單來說,在建立 Admin
時我們會多做兩件事:
- 用
extends
表示我們想要繼承的那個 class(User
)
- 為了建立新的 property 給
Admin
,我們會在 constructor
中使用 super
建立原本 User
中應有的 property。(如果沒有這個需求的話其實可以省略這個步驟)
最後用 Admin
建立的出來的 Instance 就會繼承 User
身上的 property 及 method(name
、email
和 login
)。
如果變成 ES5 的形式的話會改成這樣:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| function User(name, email) { this.name = name this.email = email }
User.prototype.login = function () { console.log(`${this.name} has logged in`) }
function Admin(role, ...args) { User.apply(this, args) this.role = role this.users = [] }
Admin.prototype = Object.create(User.prototype)
Admin.prototype.addUser = function (user) { this.users = this.users.map((user) => ({ ...user })).concat(user) } Admin.prototype.deleteUser = function (user) { this.users = this.users.filter((u) => u.name !== user.name) }
const user1 = new User('peanu', 'peanu@peanu.dev') const user2 = new User('ppb', 'ppb@peanu.dev') const admin = new Admin('root', 'admin', 'admin@peanu.dev')
admin.login() admin.addUser(user1) admin.addUser(user2) console.log('added users', admin.users) admin.deleteUser(admin.users[0]) console.log('deleted users', admin.users)
|
拆開語法糖的包裝後:
- 建立 property 的方式會從
super(...args)
變成 User.apply(this, args)
- 建立 method 的方式會從
extends User
變成 Admin.prototype = Object.create(User.prototype)
其實背後在做的事情都一樣,只是 ES6 把它包裝成更好看一點而已。