建立型-原型模式

2023-03-09 18:00:51

定義

 使用原型範例指定建立物件的種類,並且通過拷貝這些原型建立新的物件。原型模式是一種物件建立型模式---百科。

 通俗的說就是原型模式是一種建立型設計模式,指定某個物件(通過某種方式)得到一個新的物件,在記憶體中擁有新的地址,得到的物件與原物件是是相互獨立的,即得到跟原物件一樣的物件

 當我們需要兩個一模一樣的範例時,使用原型模式非常方便,如果不使用原型模式,按照建構函式的方式初始化物件,我們需要傳兩次一模一樣的引數如:

const dog = new BydCard('byd', '漢', '30w', '2023款')
const dog_copy = new BydCard('byd', '漢', '30w', '2023款')
// 使用原型模式
const dog_copy1 = Object.create(dog)

實現思路

  通過目標物件得到一個全新的新物件,使新物件也具備跟目標物件一樣的能力,這種一般思路有兩種

  1. 深拷貝
  2. 指標參照:自身物件找不到,通過內部屬性參照到目標物件上去找類似連結串列結構的next 指標

其中大多數後臺語言如java 有相關克隆介面規範,javaScript 是通過第二種方式來實現的。

javaScript 中的原型模式

  在原型模式下,當我們想要建立一個物件時,會先找到一個物件作為原型,然後通過克隆原型的方式來建立出一個與原型一樣(共用一套資料/方法)的物件。在 JavaScript 裡,Object.create方法就是原型模式的天然實現——準確地說,只要我們還在藉助Prototype來實現物件的建立和原型的繼承,那麼我們就是在應用原型模式

  有的設計模式資料中會強調,原型模式就是拷貝出一個新物件,認為在 JavaScript 類裡實現了深拷貝方法才算是應用了原型模式。事實上在 JavaScript 中,通過指標的方式也可以得到目標物件、屬性、方法的共用。克隆(深度拷貝)是實現這個目的的方法,但不是唯一的方法,也不是javaScript 的目的。

通過指標來參照,然後動態執行的時候繫結上下文 this,這樣就不會造成範例之間的錯亂,我覺得這也是this 被設計成動態繫結的原因之一。

原型模式-程式設計正規化

  原型模式不僅是一種設計模式,它還是一種程式設計正規化(programming paradigm),是 JavaScript 物件導向系統實現的根基,原型程式設計正規化的體現就是基於原型鏈的繼承。即便現在es6+ 推出了class 關鍵字,支援了類的寫法。引入的 JavaScript 類本質上還是基於原型的繼承的語法糖(class 只是一個語法糖)。類語法不會為 JavaScript 引入新的物件導向的繼承模型。 當我們嘗試用 class 去定義一個 Dog 類時:

class Dog {
  constructor(name ,age) {
   this.name = name
   this.age = age
  }
  
  eat() {
    console.log('肉骨頭真好吃')
  }
}

其實完全等價於寫了這麼一個建構函式:

function Dog(name, age) {
  this.name = name
  this.age = age
}
Dog.prototype.eat = function() {
  console.log('肉骨頭真好吃')
}

原型鏈核心點

  每個建構函式都擁有一個prototype屬性,它指向建構函式的原型物件,這個原型物件中有一個 constructor 屬性指回建構函式;每個範例都有一個內部屬性__proto__屬性,當我們使用建構函式去建立範例時,範例的__proto__屬性就會指向建構函式的原型物件。

// 輸出"肉骨頭真好吃"
dog.eat()
// 輸出"[object Object]"
dog.toString()

明明沒有在 dog 範例裡手動定義 eat 方法和 toString 方法,它們還是被成功地呼叫了。這是因為當我試圖存取一個 JavaScript 範例的屬性、方法時,它首先搜尋這個範例本身;當發現範例沒有定義對應的屬性、方法時,它會轉而去搜尋範例的原型物件;如果原型物件中也搜尋不到,它就去搜尋原型物件的原型物件,這個搜尋的連結串列就叫做原型鏈

Object 是所有的基礎類別,其中Object.prototype指向null,這樣原型鏈就有終點了,而不是無腦的一直下去。

原型鏈其他關鍵點:

  1. 所有函數(普通函數,建構函式,內建的函數)都是內建函數(類)Function 的範例,所以存在函數.__proto__ === Function.prototype 所有函數都可以直接呼叫Function原型上的方法(call / apply /bind)
  2. Function 確實很厲害,他不僅是函數的類,還是自己的類。函數是Function 的範例,Function 也是Function 的範例 Object.__proto__ === Function.prototypeFunction.__proto__===Function.prototype
  3. 物件的原型鏈最終指向Object.prototype, object.prototype._proto_ 指向null

如下程式碼驗證了這些結論:

function sayHi () {
    // console.log('hello joel')
}

// 所有函數都是Function 的範例即函數也是物件,
// 所以存在函數.__proto__ === Function.prototype
console.log(sayHi.__proto__ === Function.prototype) // true
console.log(Object.__proto__ === Function.prototype) // true
console.log(String.__proto__ === Function.prototype) // true
console.log(Array.__proto__ === Function.prototype) // true
console.log(Number.__proto__ === Function.prototype) // true
console.log(Symbol.__proto__ === Function.prototype) // true

// Function.prototype 內部屬性又指向Object的原型物件
console.log(Function.prototype.__proto__ === Object.prototype) // true
//  Function 也是Function 的範例
console.log(Function.__proto__ === Function.prototype)
// 物件最終指向object的原型
console.log(new sayHi().__proto__ instanceof Object) // true
console.log(new sayHi().__proto__ === sayHi.prototype) // true
console.log(Array.prototype.__proto__ === Object.prototype) // true
console.log(Object.__proto__.__proto__ === Object.prototype) // true

// 內建的array,string,number,object 等都是建構函式,同時也是物件
console.log(typeof Array) // function
console.log(typeof Object) // function

// 通過原型鏈找到object.prototype 上的方法
sayHi.valueOf() 

小結

  1. 原型是 JavaScript 物件導向系統實現的根基,在這裡更像是一種程式設計正規化
  2. 在JavaScript 中原型模式無處不在,只要使用原型的模型建立物件就是在使用原型模式
Object.__proto__ === Function.prototype
Function.__proto__=== Function.prototype
Function.prototype.__proto__ === Object.prototype
Object.prototype.__prto__ === null