原型和原型鏈的主要做用:javascript
因此下面的例子全是以構造函數爲例。java
函數是也是對象,是一個屬性的集合,因此函數下也有屬性,也能夠自定義屬性。當咱們建立一個函數時就默認會有一個prototype
屬性,這個屬性是一個對象(屬性的集合)。這個東西就是原型---經過調用構造函數而建立的那個對象實例的原型對象。prototype
裏也有個屬性constructor
,指向的是函數自己。git
function Person() { } Person.prototype.name='erdong'; var p1=new Person(); var p2=new Person(); console.log(p1.name); // erdong console.log(p2.name); // erdong
函數Person
有個prototype
屬性,給這個屬性添加一個name
的屬性。p1 和 p2 爲這個函數的實例,當訪問 p1.name 和 p2.name 時其值都是 prototype
下面 name的值。這個prototype
對象就是 p1 和 p2的實例原型,它下面的因此屬性和方法 p1 和 p2 均可以獲取並使用。github
看一下原型對象與構造函數的關係:瀏覽器
那麼實例是怎樣與原型對象作關聯的呢?函數
每一個JavaScript
對象都具備的一個屬性 -- __proto__
這個屬性指向該對象的原型。不過它是一個隱式屬性,並非全部瀏覽器都支持它,咱們能夠把它看作一種實例與實例原型之間的聯繫橋樑。學習
function Person() { } var p1 = new Person(); console.log(p1.__proto__==Person.prototype); // true
上述p1.__proto__
與原型對象時相等的,因而可知p1.__proto__
指向的是原型對象。this
每一個函數都有一個prototype
屬性,而prototype
下都有一個constructor
屬性,它指向prototype
所在函數。spa
function Person() { } console.log(Person.prototype.constructor==Person) // true
以上就是關於原型幾個重要的"屬性"已經說完了,下面來說講原型連。prototype
JavaScript
規定,當讀取對象的某個屬性或方法時,先從自身查找,若是找不到就去其__proto__
指向的原型對象上去找,若是找不到就去原型對象的原型對象上查找,若是再找不到就去原型對象的原型對象上去找... , 就這樣直到找到最上層,至於哪裏是最上層,下面會提到。
function Person() { } var p1 = new Person(); console.log(p1.name);// undefined p1.show();// Uncaught TypeError: p1.show is not a function
上述例子,p1 爲構造函數 Person的實例,當訪問 p1 的 name 屬性和 show 方法時,由於 p1 是剛 new 出來的實例,因此並無找到。看下面的例子:
function Person() { } Person.prototype.name='erdong'; Person.prototype.show=function () { console.log(this.name); } var p1=new Person(); console.log(p1.name);// erdong p1.show();// erdong
當在 prototype
上添加 name 屬性和 show方法後,p1 就能夠正確的訪問,這就說明 p1 在查找屬性(方法)時,在自身沒有找到 就會去__proto__
所指的原型對象上去查找。再來看一個例子:
function Person() { } Person.prototype.name='erdong'; Person.prototype.show=function () { console.log(this.name); } var p1=new Person(); p1.name = 'chen' console.log(p1.name);// chen
當咱們在 p1(對象) 上添加一個屬性 name 這個時候再去訪問 p1.name 那麼輸出的就是 "chen" 而不是 "erdong"。 這就是一個對象查找屬性(方法)時的一個規則。
咱們在上述例子查找 p1的name 時當查找到 Person.prototype 還未找到時,咱們應該還往下查找,下一級是誰呢? 由於 Person.prototype
是對象,那麼他就有一個__proto__
屬性,指向的是其原型對象--也就是其對應構造函數的 prototype
。那麼Person.prototype
是誰呢?是Object
,由於對象能夠經過 new Object()
建立:
var obj = new Object(); // 咱們平時都是經過字面量的形式來寫:var obj = { }; 其實就至關於 new Object(); 只不過是javascript在內部執行了。 obj.name = 'erdong'; console.log(obj.name); // erdong
看圖:
爲何當查找到 Object.prototype 找不到就輸出 undefined 了呢?
由於當在 Object.prototype 也找不到 name 屬性,就會去 Object.prototype 指向的原型對象上查找,咱們在上面提到,對象與其原型對象是經過 __proto__
作關聯的,可是javascript
中規定,Object.prototype.__proto__
是不存在的 也就是null
console.log(Object.prototype.__proto__ === null); // true
這一點要牢記。
原型鏈也是JavaScript
中很重要的一個概念,之因此說是一個概念,是由於它是不存在的,不像一個對象的屬性,或者是一個對象的方法同樣實例存在。
個人理解就是--一個(實例)對象的屬性或者方法的查找規則。這個規則能夠很簡單,也能夠很複雜。
咱們把上面全部的知識總結一下:
每一個函數都有一個原型對象(prototype),原型對象又包含一個屬性(constructor),指向的是函數自己,函數的實例都有一個隱式原型(__proto__),指向的是構造函數的原型對象(prototype)。
查找規則:當咱們訪問實例的一個屬性時,先從實例自身查找,若是找不到就去其內部指向的原型對象上去查找,若是再找不到,就去其內部指向的原型對象內部指向的原型對象上去查找,就這樣一直找到原型的最頂端。
看圖:
藍色的線就表示一條原型鏈。
function Person() { } Person.prototype={ name: 'erdong', sex: '男', doSoming: function() { console.log(this.name); } } var p1=new Person(); p1.doSoming(); console.log(p1.__proto__==Person.prototype); // true console.log(Person.prototype.constructor==Person);// false
上面的例子,將構造函數的prototype
屬性重寫了。雖然p1也能找的到name,可是prototype
下的constructor
屬性再也不指向Person
了。實際上指向了Object
console.log(Person.prototype.constructor==Object); //true
是由於咱們重寫了Person
的prototype
,此時Person.prototype
只是一個普通的對象。即:
Person.prototype.constructor = Person.prototype.__proto__.constructor = Object.prototype.constructor = Object
當constructor
屬性很重要時,咱們能夠這樣作:
function Person() { } Person.prototype={ constructor: Person, // 主動加上constructor屬性 name: 'erdong', sex: '男', doSoming: function() { console.log(this.name); } } console.log(Person.prototype.constructor==Person);// true
上述代碼實例能夠適用於當構造函數擁有不少方法或者屬性時的寫法。
--
原型鏈是實現繼承的一種方式。這裏只是略提一下,下面的文章會詳細理解。
當咱們不去徹底重寫函數的prototype
屬性,而是讓它等於另外一個構造函數的實例時結果會怎樣呢?
function SuperType() { } SuperType.prototype.name = 'erdong'; SuperType.prototype.getName=function() { return this.name; } function SubType() { } SubType.prototype=new SuperType(); var instance=new SubType(); console.log(instance.name); // erdong console.log(instance.getName()); // erdong
上述例子中有兩個構造函數SuperType
和SubType
,SuperType
原型上有個name屬性和getName 方法。instance
是另外一個構造函數SubType
的實例,本來instance
跟SuperType
是沒有關係的。可是如今instance
能夠獲取到 name 屬性和 getName 方法。緣由就是SubType
重寫了prototype
屬性,讓它的值等於SuperType
的實例。因此存在SuperType.prototype
中的屬性和方法,如今也存在與SubType.prototype
中。
看下面的圖:
藍色的線爲SubType.prototype
改變後的原型( __proto__ 和prototype)的指向(也是實例查找屬性的路線),紅色爲原來的原型( __proto__和prototype)的指向。
JavaScript高級程序設計一書中解釋上述的示例爲原型鏈的基本概念--當咱們讓原型對象等於另外一個構造函數的實例,此時的原型對象將包含一個指向另外一個原型的指針,相應的,另外一個原型中也包含着一個指向另外一個構造函數的指針。加入另外一個原型又是另外一個構造函數的實例,那麼上述關係依然成立,如此層層遞進,就構成了實例與原型的鏈條。這就是所謂原型鏈的基本概念。
isPrototypeOf、getPrototypeOf、instanceof、in、hasOwnProperty
用於判斷傳入的對象內部是否有一個原型對象的指針。
function Person(){ } var p = new Person(); console.log( Person.prototype.isPrototypeOf( p ) );// true
咱們上面講到實例與原型對象是經過__proto__
作關聯的,__proto__
並非Javascript
規範,因此咱們現實中不能使用它來判斷實例與原型對象的關係,這個時候就用isPrototypeOf
。
ES6 Object
新增方法,返回的是傳入對象的原型。
function Person(){ } var p = new Person(); console.log( Object.getPrototypeOf(p)===Person.prototype );// true
上述代碼輸出的是 true 證實 Object.getPrototypeOf(p)
獲取到的就是 p 的原型。
判斷前者是不是後者的一個實例。
function Person(){ } var p = new Person(); console.log(p instanceof Person); // true console.log(p instanceof Object); // true
因爲 p 既是 Person
的實例,同時它也是一個對象,因此也是Object
的實例。
看下面的示例:
console.log(Function instanceof Object); // true console.log(Object instanceof Function); // true
Function
既是Object
的實例,Object
又是Function
的實例。是有點繞了,下面會說明這一狀況。
判斷前者是不是後者原型鏈中的一個屬性。
function Person() { } Person.prototype.name='erdong'; var p=new Person(); p.sex='男'; console.log('sex' in p); // true console.log('name' in p); // true console.log('address' in p); // false
檢測傳入的字符串是不是調用者的自身屬性,若是是自身的屬性,返回true,若是是原型中的屬性或者不存在,返回false。
function Person() { } Person.prototype.name='erdong'; var p=new Person(); p.sex='男'; console.log(p.hasOwnProperty('name'));// false console.log(p.hasOwnProperty('sex')); // true console.log(p.hasOwnProperty('address')); // false
與 in 不一樣的是若是該屬性存在於實例上包括原型鏈上,就返回true,而hasOwnProperty只有是自身的屬性,纔會返回true。
咱們(構造)函數也是對象,上面說過對象下面都會有一個__proto__
屬性,那麼函數的__proto__
指向誰呢?
console.log(Person.__proto__===Function.prototype); // true
函數都是經過 new Function()
來建立的,雖然咱們平時建立函數並非經過 new
下面這個函數:
function sum (num1, num2) { return num1 + num2; }
其實在JavaScript
內部應該是這樣實現的:
var sum = new Function("num1", "num2", "return num1 + num2");
因此Person
對應的構造函數應該是Function
。
那麼新的問題又來了?
Function
也一個函數,也是一個對象,那麼他一樣也有__proto__
屬性,也有prototype
屬性,它們分別指向什麼呢?
console.log(typeof Function); // 'function' console.log(Function.__proto__ === Function.prototype); // true
看到上面是否是會很奇怪?下面解釋一下:
Function
是一個函數,它也是經過new Function
建立的,因此它是被自身建立的,它的__proto__
指向的自身的prototype
--也就是Function.prototype
。
那麼Function.prototype.__proto__
又指向誰呢?
console.log(Function.prototype.__proto__ === Object.prototype)
很顯然,Function.prototype.__proto__
是一個對象,因此它指向的是Object.prototype
。
還有一個問題,Object
也是一個構造函數,也是一個對象,那麼它應該也有prototype
和__proto__
屬性,咱們在上面說到 Object.prototype. __proto__
爲null
,那麼Object.__proto__
指向誰呢?
console.log(Object.__proto__ === Function.prototype); // true
上面Object.__proto__
又指向了Function.prototype
,是由於Object
是函數,因此它的原型就是Function.prototype
。
最後總結一下:
看似關係很複雜,其實一條一條捋清楚就有一種恍然大悟的感受。
若是文中有錯誤,請務必留言指正,萬分感謝。
點個贊哦,讓咱們共同窗習,共同進步。