JS中的原型能夠類比於Java中的父類。javascript
在Java中實現繼承有接口繼承與實現繼承兩種方式,接口繼承只繼承方法簽名,而實現繼承則繼承實際的方法。java
因爲JS中的函數沒有簽名,在ECMAScript
沒法實現接口繼承,ECMAScript
只支持實現繼承,而其實現繼承的主要依靠原型與原型鏈來實現的。函數
咱們建立的每一個函數都有一個prototype(原型)
屬性,這個屬性是一個指針,指向一個對象(原型對象),而這個對象的用途是包含能夠由特定類型的全部實例共享的屬性和方法。以下面例子所示:ui
function Person(){
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
alert(this.name);
};
var person1 = new Person();
person1.sayName(); //"Nicholas"
var person2 = new Person();
person2.sayName(); //"Nicholas"
alert(person1.sayName == person2.sayName); //true
複製代碼
在上面代碼中,咱們將sayName
方法直接添加到Person
的prototype
屬性中,構造函數爲空,此時經過構造函數生成的person1
與person2
兩個實例對象,都可共享Person.prototype
的屬性與方法。this
不管何時,只要建立了一個新函數,就會根據一組特定的規則爲該函數建立一個prototype
屬性,這個屬性指向函數的原型對象。在默認狀況下,全部原型對象都會自動得到一個constructor(構造)
屬性,這個屬性是一個指向prototype
屬性所在函數的指針。拿上面的代碼示例來講,Person.prototype.constructor
指向Person
,經過Person.prototype
可繼續爲原型對象添加方法與屬性。spa
建立了自定義的構造函數後,其原型對象默認只會取得constructor
屬性,至於其餘方法,都是從Object
繼承而來的。當調用構造函數建立一個新實例後,該實例的內部將包含一個指針(內部屬性[[Prototype]]
),指向構造函數的原型對象。雖然在腳本中沒有標準方式訪問[[Prototype]]
,但每一個對象上支持一個屬性__proto__
,可經過該屬性訪問原型對象。prototype
須要注意的是,
__proto__
這個鏈接存在於實例對象與原型對象之間,而不是存在於實例於構造函數之間。3d
之前面使用Person
構造函數建立實例對象的代碼爲例,下圖展現了Person
與Person1
、Person2
及原型對象之間的關係。 指針
對於判斷對象之間是否存在原型關係,有如下三種方式實現。code
__proto__
alert(person1.__proto__ == Person.prototype) //true
alert(person2.__proto__ == Person.prototype) //true
複製代碼
因爲
person1.__proto__
與person2.__proto__
都指向原型對象,而Person.prototype
也指向原型對象,因此返回值都爲true
isPrototypeOf()
alert(Person.prototype.isPrototypeOf(person1)); //true
alert(Person.prototype.isPrototypeOf(person2)); //true
複製代碼
A.isPrototypeOf(B)
,判斷A
是不是B
的原型對象。對於person1.__proto__.isPrototypeOf(person1)
的返回值,也是爲true,由於person1.__proto__
等於Person.prototype
。
getPrototypeOf()
alert(Object.getPrototypeOf(person1) == Person.prototype); //true
alert(Object.getPrototypeOf(person1).name); //"Nicholas"
複製代碼
Object.getPrototypeOf(person1)
返回person1
的原型對象。
每當代碼讀取某個對象的屬性時,都會執行一次搜索,目標是給定名字的屬性。搜索首先從實例對象自己開始,若是對象自己存在該屬性,則直接返回該屬性的值,若是沒有找到,則繼續搜索該對象的原型對象,如有就返回屬性值,若是都沒找到,則會返回undefined
前面提到過,原型對象最初只包含
constructor
屬性,而該屬性也是共享的,所以能夠經過實例對象訪問
alert(person2.constructor) //function Person(){}
複製代碼
調用person2.constructor
時返回function Person(){}
,證實了原型對象的constructor
屬性確實指向構造函數。
雖然能夠經過實例訪問保存在原型中的值,但卻不能經過實例對象重寫原型中的值。若是咱們在實例中添加了一個屬性,而該屬性與實例原型中的一個屬性同名,那咱們就在實例中建立該屬性,該屬性會屏蔽原型中的那個屬性,以下所示。
person1.name = "Greg";
alert(person1.name); //"Greg" ——來自實例
alert(person2.name); //"Nicholas" ——來自原型對象
複製代碼
當爲對象添加一個屬性時,這個屬性就會屏蔽原型對象中保存的同名屬性,雖然原型對象中的同名屬性依舊存在;想要取消屏蔽,可使用delete
操做符徹底刪除實例對象中的屬性,而後才能訪問原型中的屬性,以下所示。
person1.name = "Greg";
alert(person1.name); //"Greg" ——來自實例
alert(person2.name); //"Nicholas" ——來自原型對象
delete person1.name;
alert(person1.name); //"Nicholas" ——來自原型對象
複製代碼
hasOwnProperty
此外,使用hasOwnProperty()
方法能夠檢測一個屬性是存在於實例中(該方法也是從Object
中繼承而來),只有在給定屬性存在於實例對象中時,纔會返回true
。
alert(person1.hasOwnProperty("name")); //false
person1.name = "Greg";
alert(person1.name); //"Greg"——來自實例
alert(person1.hasOwnProperty("name")); //true
alert(person2.name); //"Nicholas"——來自原型
alert(person2.hasOwnProperty("name")); //false
delete person1.name;
alert(person1.name); //"Nicholas"——來自原型
alert(person1.hasOwnProperty("name")); //false
複製代碼
in
操做符此外,使用in
操做符能夠檢測一個屬性是存在於實例或其原型對象中,存在返回true
。
alert(person1.hasOwnProperty("name")); //false
alert("name" in person1); //true
複製代碼
對於屬性name
,它存在於person1
的原型對象中,但不存在於person1
實例中,因此調用hasOwnProperty
方法返回false
,調用in
操做符返回true
.
組合使用
hasOwnProperty
與in
操做符可正確判斷屬性是存在於實例中仍是原型對象中。
function Person(){
}
Person.prototype = {
constructor: Person,
friends : ["Shelby", "Court"],
};
var person1 = new Person();
var person2 = new Person();
person1.friends.push("Van");
alert(person1.friends); //"Shelby,Court,Van"
alert(person2.friends); //"Shelby,Court,Van"
alert(person1.friends === person2.friends); //true
複製代碼
person1
對屬性friends
的任何操做,都會立馬反應到person2
上。