JavaScript 進階 04:物件導向與 Prototype


物件導向(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()







你可能感興趣的文章

2048 專案

2048 專案

學 JavaScript 的那些筆記

學 JavaScript 的那些筆記

[ Week4 ] - 網路基礎概論:關於 HTTP

[ Week4 ] - 網路基礎概論:關於 HTTP






留言討論