使用原型範例指定建立物件的種類,並且通過拷貝這些原型建立新的物件。原型模式是一種物件建立型模式---百科。
通俗的說就是原型模式是一種建立型設計模式,指定某個物件(通過某種方式)得到一個新的物件,在記憶體中擁有新的地址,得到的物件與原物件是是相互獨立的,即得到跟原物件一樣的物件
當我們需要兩個一模一樣的範例時,使用原型模式非常方便,如果不使用原型模式,按照建構函式的方式初始化物件,我們需要傳兩次一模一樣的引數如:
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 裡,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
)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()
Object.__proto__ === Function.prototype
Function.__proto__=== Function.prototype
Function.prototype.__proto__ === Object.prototype
Object.prototype.__prto__ === null