原型和原型鏈
在js中,所有的變量都有原型,原型也可以有原型,原型最終都指向Object
什么是原型
在js中,一個變量被創建出來,它就會被綁定一個原型;比如說,任何一個變量都可以使用console.log打印,這里是調用了它的toString方法,而變量被創建后可能并沒有設置toString方法,但是它任然可以打印,這就從原型中獲取的toString方法
?所以可以得到第一點:原型可以提供方法給實例的變量,
原型也是一個對象,或者說對象可以作為原型并賦值給其他變量,這樣對象成為了變量的原型,而對象本身也有原型,此時就形成了 ‘鏈’
同樣的這個變量也是一個對象,他也可以作為其他變量的原型,這樣‘鏈’就變得更長了,但是所有的鏈都有一個最終指向(root終點)--- Object,Obejct是最原始的對象,它包含了所有js變量都共享的方法,它不再有原型屬性[[prototype]], 所以在js中一切皆對象中的對象就是指的繼承自Object
這里可以總結一下:
- 原型是一個對象,它可以向繼承了自身的變量提供方法(屬性)
- 變量都擁有原型,也可以成為原型,循環下去可以形成‘鏈’,
- 鏈的最頂端是Object對象,它提供了最基本的方法
js通過原型來復用通用的方法和屬性,這樣極大的減少了變量創建的成本(減少了內存支出,原型的方法屬性都存儲在原型上,變量中不會復制過來占用內存;也不必每個對象都去書寫基本的方法),
tips:
原型鏈:就前面說到的 變量獲得原型,原型又有原型,變量又可以作為原型,這樣就形成了‘鏈’,鏈的最外層(底層),擁有最多的方法,繼承了原型鏈上所有原型的方法和屬性
prototype
在 JavaScript 中,對象有一個特殊的隱藏屬性?[[Prototype]]
(如規范中所命名的),它要么為?null
,要么就是對另一個對象的引用。當我們從?object
?中讀取一個缺失的屬性時,JavaScript 會自動從原型中獲取該屬性。這就是繼承,object也可以從中借用方法
prototype不能在變量中被直接引用,通常沒有辦法遍歷或者讀取這個屬性,所以它在控制臺的名稱有些特殊,使用[[ ]]引用,但是可以直接去調用它里面的方法,當你使用變量中不存在的屬性或者方法,此時會自動向原型中尋找,對應的屬性方法并調用,若原型鏈中沒有則返回undefined
但是也有特例,在構造函數中,可以給構造的實例添加原型屬性或方法
__proto__
這是一個過時的屬性,現在只能在瀏覽器中使用,它的作用就是指向prototype,給原型添加屬性或方法,這樣添加的原型屬性或方法會被遍歷出來(Object.keys,for in),
let animal = {eats: true
};
let rabbit = {jumps: true
};rabbit.__proto__ = animal; // 設置 rabbit.[[Prototype]] = animal
注意,
__proto__
?與內部的?[[Prototype]]
?不一樣。__proto__
?是?[[Prototype]]
?的getter/setter;獲取和設置原型可以使用函數?Object.getPrototypeOf/Object.setPrototypeOf
?來替代?__proto__
?去 get/set 原型
new
當使用?new
?關鍵字調用函數時,該函數將被用作構造函數。new
?將執行以下操作:
- 創建一個空的簡單 JavaScript 對象作為實例。
- 如果構造函數的?
prototype
?屬性是一個對象,則將實例的 原型[[Prototype]] 指向構造函數的?prototype
?屬性,否則實例將保持為一個普通對象,其 [[Prototype]]為?Object.prototype,
?因此,通過構造函數創建的所有實例都可以訪問添加到構造函數?prototype
?屬性中的屬性/對象。- 使用給定參數執行構造函數,并將this指向實例
- 如果構造函數返回非原始值,則該返回值成為整個?
new
?表達式的結果。否則,如果構造函數未返回任何值或返回了一個原始值,則返回實例。(通常構造函數不返回值,但可以選擇返回值,以覆蓋正常的對象創建過程。)
?new的關鍵點在于,它新建了一個實例對象,同時引入了原型,改變了構造函數的this指向,讓實例對象成為了這個構造函數的構造結果
class
class是es6新增的語法糖,它簡化了js中構造類的步驟,隱去了對原型的操作(根本上還是原型的操作),js中的類是基于原型的,使用原型達到繼承的效果,
以下使用原型prototype和類class構建一個相同的類,
prototype.js
// 使用原型構造一個P類// 構造器,大寫開頭規范(不強制)
function P (x,y){// this指向實例,此時this不生效,構造實例后指向實例this.x = x;this.y = y;// 實例方法,此處的this指向實例,可以使用實例的屬性this.getXY = ()=>{return [this.x,this.y]}
}// 實例方法,此處的this指向windows,不能使用實例的屬性
P.prototype.getPName = ()=>{return P.name
}// 實例屬性
P.prototype.desc = '2維坐標'//類方法/靜態方法
P.getP = (p1,p2)=>{return [p1.getXY(),p2.getXY()]
}// 類屬性/靜態屬性
P.des = '坐標'
class.js?
// 使用class構造一個P類class P{//構造器constructor(x,y){this.x = x;this.y = y;}// 實例屬性desc = '2維坐標'// 實例方法getXY(){return [this.x,this.y]}getPName(){return P.name}// 靜態屬性static des = '坐標'// 靜態方法static getP(p1,p2){return [p1.getXY(),p2.getXY()]}}
很明顯,class的用法更加整體化,但實際上這兩種的寫法效果是完成相同的,
可以使用同一串代碼來實例化這個類P
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>prototype,class</title>
</head><body><script src="class.js"></script><!-- <script src="prototype.js"></script> --><script>// new關鍵字,新建一個對象,原型指向類,獲得構造器中的this指向let p = new P(1, 2);console.log(p);console.log(p.x);console.log(p.y);console.log(p.desc);console.log(p.getXY());console.log(p.getPName());console.log(P.getP(p, p));console.log(P.des);</script>
</body></html>
實現的效果是一致的方法上略有不同,對于類的構造,關鍵在于this的指向問題,屬性和方法是掛在類名下的,還是掛在實例下的需要區分開,
掛在類下的屬性方法被稱為,類方法(屬性)或者靜態方法(屬性),通過類名引用(Math.max)
掛在實例下的屬性方法被稱為,實例方法(屬性),通過實例引用(arr.push)
總結
- 在 JavaScript 中,所有的對象都有一個隱藏的?
[[Prototype]]
?屬性,它要么是另一個對象,要么就是?null
。 - 通過?
[[Prototype]]
?引用的對象被稱為“原型”。 "prototype"
?屬性僅當設置在一個構造函數上,并通過?new
?調用時,才具有這種特殊的影響。在常規對象上,prototype
?會被當成是一個普通的屬性名- 所以變量都通過原型/原型鏈使用共享的方法(即自身不存在,從原型/原型鏈中借用)
- 類的使用要區分實例和靜態類,哪些方法屬性在類名下,哪些方法屬性在實例下