物件導向(Object-Oriented Programming)
參考 Closure 最後 createWallet()
範例。原先的版本,非本人也可以更動變數 money 的值;調整成物件導向的方式,呼叫函式的方式就有所不同,是將 myWallet()
以函式的形式回傳,將函式當作物件一樣使用。運用上比較直覺跟模組化,也可將我們想要隱藏的資訊藏起來。
在物件導向的世界,不太會有直接呼叫函式的現象,例如:deduct(myWallet)
;而是會有一個所謂的對象(物件)來對他進行操作,例如:myWallet.deduct(100)
。
function createWallet(initMoney) { // 初始值
var money = initMoney // 宣告變數保留初始值
return {
add: function(num) {
money += num
},
deduct: function(num) {
if(num >= 10) {
money -= 10
} else {
money -= num
}
},
getMoney() {
return money
}
}
}
var myWallet = createWallet(99)
myWallet.add(1)
myWallet.deduct(100)
console.log(myWallet.getMoney)
物件導向的實作: ES5 VS ES6
ES6
// 設計圖
class Dog { // 大寫開頭
// setter
setName(name) {
this.name = name //this 誰呼叫他,就指向他。 d = this
}
// getter
getName() {
return this.name
}
sayHello() {
console.log(this.name)
}
}
// 實體化(instance)
var d = new Dog()
d.setName('Mary')
d.sayHello()
console.log(d.getName())
- 想要創造出很多隻狗
class Dog {
// 建構子
constructor(name) {
this.name = name
}
getName() {
return this.name
}
sayHello() {
console.log(this.name)
}
}
var a = new Dog('Mary') // 初始化的時候,同時也去 call constructor()
d.sayHello()
var b = new Dog('Henry')
b.sayHello() // 使用同樣的 method
ES5(沒有 class 的語法)
function Dog(name) {
var myName = name
return {
getName: function() {
return myName
},
sayHello: function() {
console.log(myName)
}
}
}
var d = Dog('abc')
d.sayHello()
var b = Dog('Leo')
b.sayHello()
console.log(d.sayHello === b.sayHello) // false
- ES5 裡,把 function 當作 constructor 使用
function Dog(name) {
this.name = name
}
var d = new Dog('Mary')
console.log(d) //印出 {name: Mary}, 相當於屬性設定
===
// 相當於 ES6
class Dog {
constructor(name) {
this.name = name
}
var a = new Dog('Mary')
d.sayHello()
- ES5 實作
function Dog(name) {
this.name = name
}
Dog.prototype.getName = function() {
return this.name
}
Dog.prototype.sayHello = function() {
console.log(this.name)
}
var d = new Dog('abc')
d.sayHello()
var b = new Dog('Leo')
d.sayHello()
console.log(d.sayHello === b.sayHello) // true
從 prototype 來看原型鍊
還記得之前 ES5 實作的例子,d.sayHello()
與 Dog.prototype.getName
需要有某些機制,才能把兩者連結在一起。而在 JS 有一個屬性是 __proto__
, 類似之前談的 scope chain。在 ES6 之後,因為有了 Class 這個語法糖可以使用,但底層運作的方式仍相同。
function Dog(name) {
this.name = name
}
Dog.prototype.getName = function() {
return this.name
}
Dog.prototype.sayHello = function() {
console.log(this.name)
}
var d = new Dog('abc')
d.sayHello()
console.log(d.__proto__)
// 如果你在 d 身上找不到 sayHello()
// 接下來去 __proto__ 找
console.log(d.__proto__)
// 印出 Dog { getName: [Function], sayHello: [Function] }
console.log(d.__proto__ === Dog.prototype)
// 印出 true
- 進一步理解:當我們呼叫 d.sayHello()
d.sayHello()
1. d 身上有沒有 sayHello
2. d.__proto__ 有沒有 sayHello
3. d.__proto__.__proto__ 有沒有 sayHello
4. d.__proto__.__proto__.__proto__ 有沒有 sayHello
5. null 找到頂了
===
d.__proto__ = Dog.prototype // new 機制
d.__proto__.__proto__ = Object.prototype
Dog.prototype.__proto__ = Object.prototype
- 測試一下
function Dog(name) {
this.name = name
}
Dog.prototype.getName = function() {
return this.name
}
Dog.prototype.sayHello = function() {
console.log('dog', this.name)
}
Object.prototype.sayHello = function() {
console.log('Object', this.name)
}
var d = new Dog('abc')
d.sayHello()
// 印出 dog abc
原型鍊
經由 __proto__
構成,串接成一連串的機制,稱為 prototype chain。
- 再練習一下
function Dog(name) {
this.name = name
}
Dog.prototype.getName = function() {
return this.name
}
var d = new Dog('abc')
console.log(Dog.__proto__ === function.prototype)
// 印出 true
- 舉個例子:toString()
console.log(Object.prototype.toString.call('123'))
// 印出 [Object string]
var a = '123';
a.toString()
console.log(a.toString())
// 印出 123
- 為何不一樣
var a = '123';
a.toString()
console.log(a.__proto__ === String.prototype)
console.log(a.toString === String.prototype.toString)
// true
由於 prototype 的原因,我們想要往上一層找 Object 的時候,就會被 String.prototype.toString
擋住;就好像是作用域的機制一樣,同層裡面有同名的函式,你就無法存取到外層的函式。
function Dog(name) {
this.name = name
}
Dog.prototype.getName = function() {
return this.name
}
String.prototype.first = function() {
return this[0]
}
var a = '123'
console.log(a.first()) // 印出 1
console.log(a.__proto__ === String.prototype) // 印出
new 究竟背後做了什麼事?
- 預備知識
function test() {
console.log(this)
}
test()
// 會是一個很大的值
test.call()
function test() {
console.log(this)
}
test.call('123')
// 此時的 this,就會是 test.call() 所傳進去的東西
// 印出 {string: 123}
- this 的原理
function Dog(name) {
this.name = name
}
Dog.prototype.getName = function() {
return this.name
}
String.prototype.first = function() {
return this[0]
}
// 目標實作出 new 這個關鍵字背後執行的事情
// var b = newDog('hello')
// b.sayHello()
- new 在做的事(初始化)
function newDog(name) { var obj = {} Dog.call(obj, name) // 建構子做的事 obj->this ; name->參數 name console.log(obj) // 印出 {name: hello} obj.__proto__ = Dog.prototype // 連結 return obj }
物件導向的繼承(Inheritance)
- 繼承者(共用屬性)
class Dog {
constructor(name) {
this.name = name
}
sayHello() {
console.log(this.name)
}
}
class BlackDog extends Dog { // 繼承,往上找他的 parent,找到上一層的 constructor 來執行
test() {
console.log('test', this.name)
}
}
const d = new BlackDog('hello')
d.test()
- super
class Dog {
constructor(name) {
this.name = name
}
sayHello() {
console.log(this.name)
}
}
class BlackDog extends Dog { // 黑狗建立時,就呼叫一個 function
constructor(name) {
super(name) // 必須要先初始化:需要呼叫上一層的 constructor = Dog.constructor
this.sayHello()
}
test() {
console.log('test', this.name)
}
}
const d = new BlackDog('hello')
d.test()