什麼是JS原型?javascript
若是對JS原型解釋的話,會涉及到兩個概念:構造函數,原型對象java
通俗的說,就是你在script標籤裏面聲明的那個函數git
<script>
function Test(){
// 我就是構造函數
}
</script>
複製代碼
在聲明瞭一個函數以後,瀏覽器會自動按照必定的規則建立一個對象,這個對象就叫作原型對象。這個原型對象實際上是儲存在了內存當中數組
在聲明瞭一個函數後,這個構造函數(聲明瞭的函數)中會有一個屬性prototype,這個屬性指向的就是這個構造函數(聲明瞭的函數)對應的原型對象;原型對象中有一個屬性constructor,這個屬性指向的是這個構造函數(聲明瞭的函數)瀏覽器
以下圖:bash
使用構造函數建立對象函數
咱們的構造函數使用new來建立對象的時候,就像下面這樣:ui
<script>
function students(){
// 我就是構造函數
}
let stu = new students();
</script>
複製代碼
此時,stu就是那個構造函數students建立出來的對象,這個stu對象中是沒有prototype屬性的,prototype屬性只有在構造函數students中有,看圖!this
能夠看出,構造函數students中有prototype屬性,指向的是students對應的原型對象;而stu是構造函數students建立出來的對象,他不存在prototype屬性,因此在調用prototype的時候的結構是undefined,但stu有一個__proto__屬性,stu調用這個屬性能夠直接訪問到構造函數students的原型對象(也就是說,stu的__proto__屬性指向的是構造函數的原型對象),看圖:spa
說明:
開始擼代碼:
<script type="text/javascript">
function students () {
}
// 可使用students.prototype 直接訪問到原型對象
//給students函數的原型對象中添加一個屬性 name而且值是 "張三"
students.prototype.name = "張三";
students.prototype.age = 20;
var stu = new students();
/*
訪問stu對象的屬性name,雖然在stu對象中咱們並無明確的添加屬性name,可是
stu的__proto__屬性指向的原型中有name屬性,因此這個地方能夠訪問到屬性name
就值。
注意:這個時候不能經過stu對象刪除name屬性,由於只能刪除在stu中刪除的對象。
*/
alert(stu.name); // 張三
var stu1 = new students();
alert(stu1.name); // 張三 都是從原型中找到的,因此同樣。
alert(stu.name === stu1.name); // true
// 因爲不能修改原型中的值,則這種方法就直接在stu中添加了一個新的屬性name,而後在stu中沒法再訪問到
//原型中的屬性。
stu.name = "李四";
alert(stu.name); //李四
// 因爲stu1中沒有name屬性,則對stu1來講仍然是訪問的原型中的屬性。
alert(stu1.name); // 張三
</script>
複製代碼
與原型有關的幾個方法:
prototype 存在於構造函數中 (其實任意函數中都有,只是否是構造函數的時候prototype咱們不關注而已) ,他指向了這個構造函數的原型對象
constructor屬性存在於原型對象中,他指向了構造函數
以下代碼:
<script type="text/javascript">
function students () {
}
alert(students.prototype.constructor === students); // true
</script>
複製代碼
用構造方法建立一個新的對象以後,這個對象中默認會有一個屬性__proto__, 這個屬性就指向了構造方法的原型對象
說完原型後,趁熱打鐵,說一下原型鏈
在js中,萬物皆對象,對象能夠說是重中之重了。每個對象都擁有本身的屬性。可是在這個世界中有不少東西都是類似的,能夠歸爲一類,他們有共同的方法和屬性。不可能讓每個對象都定義一個屬性吧。那樣太消耗內存了。因此,在js中怎麼才能讓多個對象共享一個或多個方法呢?原型的出現就是爲了解決這個問題。
一切皆對象,函數是特殊的對象。
全部的引用類型(函數、數組和對象)都擁有__proto__屬性(隱式原型)
全部函數擁有prototype屬性(顯示原型)
原型對象:擁有prototype屬性的對象,在定義函數時就被建立
大多數狀況下,__proto__能夠理解爲「構造器的原型」(__proto__ === constructor.prototype)
function Word(words){
this.words = words
}
Word.prototype = {
alert(){
alert(this.words)
}
}
var w = new Word("Hello Word");
w.alert();
console.log(w.__proto__ === Word.prototype) // true
複製代碼
實例w的隱式原型指向它構造函數的顯示原型。(指向即恆等於)
w.__proto__ === Word.prototype // true
複製代碼
當調用某種方法或查找某種屬性時,首先會在自身調用和查找,若是自身沒有該屬性或方法,則會去它的__proto__屬性中調用查找,也就是構造方法的prototype屬性中查找。實例經過這樣的方法繼承構造函數的方法和屬性。
實例經過__proto__屬性指向構造方法的prototype的屬性實現方法和屬性的繼承。
因爲__proto__屬性是任何對象都有的屬性,在js中一切皆對象,因此會造成一條__proto__鏈接起來的鏈條,遞歸訪問__proto__必須最終到頭,而且值爲null。
Function.prototype.a = 'a';
Object.prototype.b = 'b';
function Person(){}
console.log(Person);
let p = new Person();
console.log(p); // Person {} 對象
console.log(p.a); // null
console.log(p.b); // b
複製代碼
說明: p是Person()的實例,是一個Person對象,它擁有一個屬性值__proto__,而且__proto__是一個對象,包含兩個屬性值constructor和__proto__,
會發現p.__proto__===Person.prototype, p.__proto__.constructor ===Person // true,即p.__proto__.constructor指向了構造函數自己。
Person.prototype的__proto__屬性,指向了Object 的prototype屬性。即p.b的打印結果爲b。則實例w經過__proto__屬性繼承了Object 的屬性b。經過__proto__屬性一直向上查找,一直到null爲止。
那麼構造函數Person的__proto__指向了Function.prototype。
事實上,js裏徹底依靠"原型鏈"模式來實現繼承。
再簡單說一下__proto__、prototype、constructor
繼承的實現方式:
爲了實現繼承,_ _proto _ _會指向上一層的原型對象,而上一層的結構依然相似,那麼就利用proto一直指向Object的原型對象上!Object.prototype. _ _ proto _ _ = null;表示到達最頂端。如此造成了原型鏈繼承。
下面有個圖很是經典:
你們能夠本身動手畫一下,加深本身的理解
大體總結一下:
1.Object是做爲衆多new出來的實例的基類 function Object(){ [ native code ] }
2.Function是做爲衆多function出來的函數的基類 function Function(){ [ native code ] }
3.構造函數的__proto__(包括Function.prototype和Object.prototype)都指向Function.prototype
4.原型對象的__proto__都指向Object.prototype
5.Object.prototype._ _ proto _ _指向null