實例化和原型
在JavaScript中,使用原型定義的屬性和功能會自動應用到對象的實例上,一但進行了定義,原型的屬性就會變成實例化對象的屬性。所有函數初始化的時候都有一個prototype屬性表示原型。
// 定義一個函數
function A() {}
// 在其原型上添加屬性
A.prototype.go = function () {return true;};
// 將函數賦值給變量,空函數無返回值
var a1 = A();
console.log(a1); // undefined
// 將函數作為構造器進行實例化賦值給變量
var a2 = new A();
// 變量成為一個實例
console.log(a2); // A {}
// 該實例繼承了函數原型上的屬性
console.log(a2.go()); // true
使用new操作符將函數作為構造器進行調用時,其上下文會被定義為新對象實例。這就表明,在構造器函數內部,可以使用this參數初始化值。在構造器內的綁定操作優先級會高于在原型上的綁定操作優先級,因為構造器的this上下文指的是實例自身。
// 使用同樣的名稱為構造器和原型添加屬性
function A() {
this.x=1;
}
A.prototype.x = 2;
// 實例繼承屬性時,構造器本身的屬性會覆蓋原型上的屬性
var a1 = new A();
console.log(a1.x); // 1
原型的改變會影響其構造器已經創建的實例對象。
// 創建構造函數并實例化
function A() {}
var a1 = new A();
// 改變其原型,已經實例化的對象上繼承的屬性也會被修改
A.prototype.x = 1;
console.log(a1.x);
每個JavaScript對象都有一個隱式屬性constructor,該屬性引用的是創建該對象的構造器。
function A() {}
var a1 = new A();
A.prototype.x = 1;
// 使用a1.constructor.prototype.x可以訪問到原型上的x屬性,證明了原型是實時附加在對象上的,所以在創建實力之后,更改原型也會生效。
console.log(a1.constructor.prototype.x); // 1
查找屬性時,首先查找對象自身,如果沒有找到,再查找構造器的原型。
function A() {
this.x=1;
this.y=2;
}
var a1 = new A();
A.prototype.y=1;
// 實例化的對象a1已經具有了y屬性,即使在實例化之后再為構造器的原型添加新的y屬性,也不會覆蓋實力中的原來的y屬性。
console.log(a1.y); // 2
利用constructor屬性,可以在不知道原有構造器的情況下,利用已有的實例來創建新的實例。
function A() {}
var a1 = new A();
var a2 = new a1.constructor();
// a1與a2指向的不是同一個實例
console.log(a1 !== a2); // true
疑難陷阱
擴展原生Object.prototype時,所有對象都會接受這些額外的屬性。當我們遍歷對象的屬性時,這些額外的屬性也會被遍歷。
// 為原生Object.prototype添加keys方法,返回一個屬性名組成的數組。
Object.prototype.keys=function () {
var key = [];
for(var p in this){
key.push(p);
}
return key;
};
var obj = {
a:1,
b:2,
c:3
};
// keys方法本身被繼承,也成為了實例obj的屬性之一
console.log(obj.keys()); // ["a", "b", "c", "keys"]
使用hasOwnProperty()方法可以判斷屬性是實例自身的,還是繼承自原型的
Object.prototype.keys=function () {
var key = [];
for(var p in this){
// 判斷屬性是來自實例自身還是繼承自原型
if(this.hasOwnProperty(p)){
key.push(p);
}
}
return key;
};
var obj = {
a:1,
b:2,
c:3
};
console.log(obj.keys()); // ["a", "b", "c"]