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