定義
使用原型例項指定建立物件的種類,並且透過複製這些原型建立新的物件。原型模式是一種物件建立型模式---百科。
通俗的說就是原型模式是一種建立型設計模式,指定某個物件(透過某種方式)得到一個新的物件,在記憶體中擁有新的地址,得到的物件與原物件是是相互獨立的,即得到跟原物件一樣的物件
當我們需要兩個一模一樣的例項時,使用原型模式非常方便,如果不使用原型模式,按照建構函式的方式初始化物件,我們需要傳兩次一模一樣的引數如:
const dog = new BydCard('byd', '漢', '30w', '2023款')
const dog_copy = new BydCard('byd', '漢', '30w', '2023款')
// 使用原型模式
const dog_copy1 = Object.create(dog)
實現思路
透過目標物件得到一個全新的新物件,使新物件也具備跟目標物件一樣的能力,這種一般思路有兩種
- 深複製
- 指標引用:自身物件找不到,透過內部屬性引用到目標物件上去找類似連結串列結構的
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,這樣原型鏈就有終點了,而不是無腦的一直下去。
原型鏈其他關鍵點:
- 所有函式(普通函式,建構函式,內建的函式)都是內建函式(類)
Function
的例項,所以存在函式.__proto__
=== Function.prototype
所有函式都可以直接呼叫Function原型上的方法(call / apply /bind
) - Function 確實很厲害,他不僅是函式的類,還是自己的類。函式是Function 的例項,Function 也是Function 的例項
Object.__proto__ === Function.prototype
,Function.__proto__===Function.prototype
- 物件的原型鏈最終指向
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()
小結
- 原型是 JavaScript 物件導向系統實現的根基,在這裡更像是一種程式設計正規化
- 在JavaScript 中原型模式無處不在,只要使用原型的模型建立物件就是在使用原型模式
Object.__proto__ === Function.prototype
Function.__proto__=== Function.prototype
Function.prototype.__proto__ === Object.prototype
Object.prototype.__prto__ === null