回到文章列表

[JS201 筆記] 物件導向基礎與 prototype

TWGD / 2019-01-07 / 閱讀時間約 1 分鐘

這是 Lidemy 課程:[JS201] 進階 JavaScript:那些你一直搞不懂的地方:物件導向基礎與 prototype 課程筆記。

如果有人不小心看到,請不要太嚴格,這只是我上課的隨筆筆記XD 若有錯誤,請不要吝嗇糾正我,謝謝~


ES6 的物件導向範例:

ES6 才有 class 這個關鍵字。

class Dog {
  // 名稱開頭通常大寫

  // 建構子,作為初始化
  constructor(name) {
    this.name = name
  }

  // setter 用來設置值
  setName(name) {
    this.name = name
  }

  // getter 用來存取值
  getName() {
    return this.name
  }

  sayHello() {
    console.log('hello' + this.name)
  }
}

let cat = new Dog('cat') // cat 是 Dog 的 instance
cat.getName()
cat.sayHello()

ES5 的物件導向:

function Dog(name) {
  var myName = name

  return {
    getName: function () {
      return myName
    },
    sayHello: function () {
      console.log('hello' + myName)
    },
  }
}

var cat = Dog('cat')
cat.getName()
cat.sayHello()

var cat2 = Dog('cat2')
cat2.getName()
cat2.sayHello()

但是以上會有一個問題:cat.getName() !== cat2.getName()。 每創建一個 instance,就要耗費一個記憶體空間。 因此改用以下的寫法:

// 等同於建構子的作用
function Dog(name) {
  this.name = name
}

// 設定 method
Dog.prototype.getName = function () {
  return this.name
}

;(Dog.prototype,
  (sayHello = function () {
    console.log('hello' + this.name)
  }))

// new 創建 Dog 的 instance
var cat = new Dog('cat')
console.log(cat) // Dog {name: 'cat'}
cat.sayHello() // hello cat

var cat2 = new Dog('cat2')
console.log(cat2) // Dog {name: 'cat'}
cat2.sayHello() // hello cat2

console.log(cat.sayHello === cat2.sayHello) // true

從 prototype 來看「原型鍊」

與 javascript 提供的屬性 __proto__ 有關:instance 透過 __proto__ 屬性得以連結 prototype

從以下例子理解 prototype chain 原型鍊:

function Dog(name) {
  this.name = name
}

Dog.prototype.getName = function () {
  return this.name
}

Dog.prototype.sayHello = function () {
  console.log('hello ' + this.name)
}

var cat = new Dog('cat')
var cat2 = new Dog('cat2')

console.log(cat.__proto__ === Dog.prototype) // true
console.log(cat.__proto__.__proto__ === Object.prototype) // true
console.log(cat.__proto__.__proto__.__proto__ === null) // true

cat.sayHello()
/*
prototype chain 原型鍊
呼叫 cat.sayHello() 的時候,找 sayHello() 的順序
1. cat 身上有沒有 sayHello
2. cat.__proto__ 有沒有 sayHello
3. cat.__proto__.__proto__ 有沒有 sayHello
4. cat.__proto__.__proto__.__proto__ 有沒有 sayHello
5. null (找到頂了)
*/

// 順便看一下 Dog 的 __proto__ 是什麼
console.log(Dog.__proto__ === Function.prototype) // true

prototype 的其他應用:

// 自訂 method 讓其他 string 使用
String.prototype.first = function () {
  return this[0]
}

var a = '123'
console.log(a.first()) // 1

new 底層做了什麼

先有 call() 的基本觀念:

function test() {
  console.log(this)
}

// call 裡面傳什麼東西,this 就會是什麼
test.call('123') // [String: '123']

以下實作 new 的原理:

function Dog(name) {
  this.name = name
}

Dog.prototype.getName = function () {
  return this.name
}

Dog.prototype.sayHello = function () {
  console.log('hello ' + this.name)
}

var cat = new Dog('cat')

/*
實作一個 function newDog(){...},來執行以下程式碼:
var cat2 = newDog('cat2');
cat2.sayHello();
*/

function newDog(name) {
  var obj = {} // 產生一個 obj
  Dog.call(obj, name) // call constructor 完成初始化物件
  obj.__proto__ = Dog.prototype // 把 obj 關聯到 Dog 的 prototype
  return obj
}

物件導向的繼承 Inheritance

繼承用在哪裡:若有共同的屬性可以直接繼承上層的,不用重複寫。

  • 注意 super() 的用法。
class Dog {
  constructor(name) {
    this.name = name
  }

  sayHello() {
    console.log('hello ' + this.name)
  }
}

class BlackDog extends Dog {
  constructor(name) {
    // super() 呼叫上層得已初始化。強制使用 super()。傳參數進去給上層的 constructor 使用。
    super(name) // Dog 的 constructor。
    this.sayHello() // 初始化的時候就 say Hello
  }

  test() {
    console.log('test:', this.name)
  }
}

const black = new BlackDog('black') // hello black
black.test() // test: black