一、理解原型對象
由特定類型創建的所有實例,可能存在公共的屬性,也可能存在實例特有的屬性。爲了保證公共屬性不重複定義,將公共屬性保存在原型對象中;爲了實例特有的屬性不共享獨立存在,則將特有的屬性保存在構造函數中。
如上原型鏈的原理圖所示,構造函數、原型對象和實例之間有以下聯繫:
- 構造函數的prototype屬性指向原型對象
- 原型對象的constructor屬性指向構造函數
- 所有實例的__proto__屬性指向原型對象
- 所有實例的constructor屬性指向構造函數(實例原來沒有constructor屬性,是從原型對象繼承的)
var Person = function(name,age){
this.name = name;
this.age = age;
};
//爲原型對象創建say方法,所有實例共享say方法
//相當於 原型對象=Person.prototype; 原型對象.say=functhon(){}
Person.prototype.say = function(){
alert(this.name);
};
var person1 = new Person('ermiao',18);
person1.say(); //輸出'ermiao'
//此時構造函數爲Person,原型對象爲Person.prototype,實例爲person1
//原型對象的constructor爲構造函數Person
alert(Person.prototype.constructor); //輸出Person
//實例的constructor爲構造函數Person
alert(person1.constructor); //輸出Person
//實例的__proto__爲原型對象Person.prototype
alert(person1.__proto__ === Person.prototype); //輸出true
注意:
- 實例的__proto__指針在ECMA-262中定義爲 [[prototype]],腳本中沒有標準的方式訪問 [[prototype]]。但是在Firefox、Safari、Chrome中規定每個對象都支持__proto__屬性。
- 創建自定義構造函數後,原型對象默認只有constructor屬性,其他都是從Object中繼承的。
- 由於實例原來沒有constructor屬性,是從原型對象繼承的,因此實例和構造函數沒有直接的關係。
二、判斷方法
構造函數、原型對象和實例之間有很多判斷方法如下:
1.實例和原型對象:
- 原型對象A.isPrototypeOf (實例A) 判斷實例A的__proto__是否爲原型對象A(返回True/False)
- Object.getPrototypeOf(實例A) 返回實例A的__proto__(ES5新增方法)
2.實例中的屬性來自哪裏?
實例能夠在構造函數中定義獨立不共享的屬性和方法,也能繼承原型對象中共享的屬性和方法,因此需要判斷實例中的屬性到底來自自己獨有的還是繼承原型對象的。
當查找實例的某個屬性時,首先查找實例自己獨有的屬性,若沒有再查找其原型對象的屬性。因此當實例和原型對象有同名屬性,會先返回實例的屬性值,原型對象中的屬性值會被‘遮蔽’
實例能夠訪問原型對象的屬性值,但卻不能改變原型對象的屬性。
- 檢測實例中是否有屬性A?
單獨使用in操作符可以判斷實例中是否有屬性A(不管是獨有還是繼承)
alert('name' in person1); //輸出true,name是獨有屬性
alert('say' in person1); //輸出true,say方法是繼承原型對象的
- 檢測實例中的屬性A是否是獨有?
實例.hasOwnProperty(“屬性名A”) 能夠判斷屬性名A是否是實例獨有屬性,但未定義和繼承屬性都返回false
alert(person1.hasOwnProperty("name")); //返回true
alert(person1.hasOwnProperty("say")); //返回false,say方法是繼承原型對象的屬性
alert(person1.hasOwnProperty("school")); //返回false,school屬性未定義
- 檢測實例中的屬性A是否是繼承了原型對象?
通過結合in操作符和hasOwnProperty函數,能夠創建一個函數直接判斷屬性是否是繼承的:
function hasPrototypeProperty(object,key){
return !object.hasOwnProperty(key) && (key in object);
}
alert(hasPrototypeProperty(person1,"say")); //輸出true,say發法是繼承的
alert(hasPrototypeProperty(person1,"name")); //輸出false,name屬性是獨有的
- 返回所有屬性
遍歷實例所有屬性的方法有:for-in循環遍歷、Object.keys()、Object.getOwnPropertyNames()
(1)for-in循環
for (var p in 對象) 返回所有能夠通過對象訪問的可枚舉(enumerated)的屬性(包含實例獨有的和原型對象繼承的)
在IE8和更早的版本,屏蔽不可枚舉屬性([[Enumerable]]=false)的實例屬性不會出現在for-in循環中。而其他版本, 會出現,因爲規定開發人員定義的屬性都是可枚舉的。ES5將constructor和prototype屬性的[[Enumerable]]設爲false。
(2)屬性數組 = Object.keys(對象)
該方法同for-in返回可枚舉的屬性數組。若對象是原型對象,返回所有屬性,若是實例返回其定義的屬性
(3)屬性數組 = Object.getOwnPropertyNames(對象)
該方法返回一個包含所有屬性的數組,不論是否可枚舉。
後兩種方法都可替代for-in
三、原型對象的修改問題
1.原型的動態性
由於實例和原型之間的鬆散連接(指針),當增減原型的屬性或方法時,實例能夠實時的改變。因爲當搜索實例的某個屬性時,首先在實例中找是否定義,若沒有則搜索實例的原型對象中是否存在。
2.自定義原型對象的修改
若採用向原型對象單獨添加屬性的方法,需要多次輸入,爲了方便可以直接定義如下:
Person.prototype = {
name:'ermiao',
age: 18,
say:function(){
alert(this.name);
}
};
但是這種定義會切斷實例和對象的連接,因爲實例的__proto__仍指向舊原型對象,構造函數指向新的原型對象。
注意:由於我們向Person的原型對象新定義了一個對象,相當於原型對象變成了由對象字面量創建的對象,此時雖然instanceOf的結果不變,但原型對象的constructor已不指向Person而是指向了object構造函數。
如果需要用的constructor屬性,那麼需要把constructor手動指向Person。原生constructor的[[enumerable]]屬性爲false,但這種賦值方式會將constructor的[[enumerable]]屬性改爲true。
2.原生對象的原型修改
原生對象如Array可以被定義原型對象,但不建議修改原生對象的原型屬性。