javascript面向對象學習筆記(二)——建立對象
工廠模式
該模值抽象了建立具體對象de過程。用函數來封裝噫特定接口建立對象的細節。
function createPerson(name,age,job){
var o=new Object();
o.name=name;
o.age=age;
o.job=job;
o.sayName=function(){
alert(this.name);
};
return o;
}
var person1=createPerson("Chiaki",,"Software Engineer");
person2=createPerson("Wu",,"Student");
特點:可以無數次調用,解決了建立多個相似對象的問題,但沒有解決對象識别的問題(即怎樣知道一個對象的類型)。
構造函數模式
建立自定義的構造函數,進而定義自定義對象類型的屬性方法。
function Person(name,age,job){
this.name=name;
this.age=age;
this.job=job;
this.sayName=function(){
alert(this.name);
};
}
var person1=new Person("Chiaki",,"Software Engineer");//person1的constructor(構造函數)屬性指向Person
var person2=new Person("Wu",,"Student");//person2的constructor(構造函數)屬性指向Person
person1和person2都有一個constructor(構造函數)屬性,該屬性指向person.
alert(person1.constructor==Person);//true
alert(person2.constructor==Person);//true
構造函數模式與工廠模式的差別:
1、沒有顯示的建立對象;
2、直接将屬性和方法賦給this對象;
3、沒有return語句;
4、構造函數函數名以大寫字母開頭,非構造函數函數名以小寫字母開頭;
5、建立構造函數的新執行個體必須使用new操作符,例如var person1=new Person(“Chiaki”,21,”Web Engineer”);
6、将來可以将他的執行個體辨別為一種特定的類型。
構造函數與其他函數的差別與聯系:兩者的調用方式不同;任何函數隻要用new操作符調用,就可以把它當作構造函數,不通過new操作符調用,則和普通函數沒什麼差別。
//将上例當作構造函數使用
var person=new Person("Chiaki",,"Software Engineer");
person.sayName();//Chiaki
//作為普通函數調用
Person("Wu",,"Student");
window.sayName();//Wu//在全局作用域中調用一個函數,this對象總是指向Global對象(即浏覽器的window對象)
//在另一個對象作用域中調用
var o=new Object();
Person.call(o,"lola",,"designer");//使用call()在對象o的作用域中調用Person()函數,調用後o就擁有了Person()函數的所有屬性和sayName()方法
o.sayName();//lola
缺點:每個方法都要在每個執行個體上重新建立一遍。
alert(person1.sayName==person2.sayName);//false
解決方法:把函數定義轉到構造函數外,但是此方法可能需要定義多個全局函數,将導緻該自定義引用類型毫無封裝性可言。
function Person(name,ago,job){
this.name=name;
this.age=age;
this.job;
this.sayName=sayName;
}
function sayName(){
alert(this.name);
}
var person1=new Person("Chiaki",,"Software Engineer");
var person2=new Person("Wu",,"Student");
//person1和person2對象共享在全局作用域中定義的同一個sayName函數
原型模式
我們建立的每一個函數都有一個property(原型)屬性,這個屬性是一個指針,指向一個對象(該對象包含可以由特定類型的所有執行個體共享的屬性和方法)。
function Person(){
}
Person.prototype.name="Chiaki";
Person.prototype.age=;
Person.prototype.job="Software Engineer";
Person.prototype.sayName=function(){
alert(this.name);
};
var person1=new Person();
person1.sayName();//Chiaki
var person2=new Person();
person2.sayName();//Chiaki
建立一個新函數就會根據特定的規則為其生成一個prototype屬性,該屬性指向函數的原型對象,原型對象自動獲得一個constructor屬性,這個屬性包含一個指向prototype屬性所在函數的指針。當調用構造函數建立一個新執行個體後,該執行個體的内部将包含一個指針(内部屬性[[Prototype]]),指向構造函數的原型對象。
可以通過對象執行個體通路儲存在原型中的值,卻不能通過對象執行個體重寫原型中的值(當為對象執行個體添加一個屬性時,這個屬性就會屏蔽原型對象中儲存的同名屬性)。
function Person(){
}
Person.prototype.name="Chiaki";
Person.prototype.age=;
Person.prototype.job="Software Engineer";
Person.prototype.sayName=function(){
alert(this.name);
};
var person1=new Person();
var person2=new Person();
person1.name="Wu";
alert(person1.name);//Wu——來自執行個體//當alert()通路person1.name時,在該執行個體上搜尋到一個名為name的屬性,則不必搜尋原型
alert(person2.name);//Chiaki——來自原型//當alert()通路person2.name時,在該執行個體上搜尋不到一個名為name的屬性,就繼續搜尋原型,結果在那裡找到了name屬性。
使用delete操作符可以完全删除執行個體屬性:。
var person1=new Person();
var person2=new Person();
person1.name="Wu";
alert(person1.name);//Wu
alert(person2.name);//Chiaki
delete person1.name;
alert(person1.name);//Chiaki//delete操作符删除了person1.name,進而恢複了對原型中name屬性的連接配接
可使用
hasOwnProperty()
方法檢測一個屬性是否存在于執行個體中,還是存在原型中。
alert(person1.hasOwnProperty("name"));//false//person1執行個體中沒有name屬性
person1.name="Wu";//person1重寫name屬性,即person1執行個體中有name屬性
alert(person1.hasOwnProperty("name"));//true//
原型與in操作符:單獨使用、在for-in循環中使用。
單獨使用時,in操作符會在通過對象能夠通路給定屬性時傳回true,無論該屬性存在執行個體中還是原型中。
alert("屬性" in 執行個體);
如果該執行個體中有該屬性則傳回true,否則傳回false。
function Person(){
}
Person.prototype.name="Chiaki";
Person.prototype.age=;
Person.prototype.job="Software Engineer";
Person.prototype.sayName=function(){
alert(this.name);
};
var person1=new Person();
var person2=new Person();
alert(person1.hasOwnProperty("name"));//false//name屬性在原型中
alert("name" in person1);//true
person1.name="Wu";alert(person1.hasOwnProperty("name"));//true//重新name屬性後,person1執行個體中有name屬性
alert("name" in person1);//true
hasPrototypeProperty() 方法判斷通路到的屬性是否存在于原型中。
var person =new Person();
alert(hasPrototypeProperty(perso,"name"));//true
person.name="Wu";//重寫person.name,原型中的name屬性被屏蔽,則通路到的name屬性來自執行個體person
alert(hasPrototypeProperty(person,"name");//false
in操作符和hasOwnProperty()方法同時使用可以判斷屬性是處于對象中還是處于原型中。
在for-in循環中使用,傳回所有能夠通過對象通路的可枚舉(enumerated)屬性,包括執行個體中的屬性和原型中的屬性。屏蔽了原型中不可枚舉屬性(即[[enumerable]]設定為false)的執行個體屬性也會在for-in循環中傳回(IE 8 及更早的版本除外).
var o={
name:"Chiaki"
};
for (var prop in o){
alert(prop);//name
}
IE早期版有個bug會導緻屏蔽不可枚舉屬性的執行個體屬性不會出現在for-in循環中。
預設不可枚舉的屬性和方法有:hasOwnProperty()、propertyIsEnumerable()、toLocaleString(),toString()和valueOf();ECMAScript5也将constructor和prototype屬性的[[Enumerable]]設定為false。
Object.keys()
方法接收一個對象作為參數,傳回一個包含所有可枚舉屬性的字元串數組。
var keys=Object.keys(Person.prototype);
alert(keys);//"name,age,job,sayName"
var p1=new Person();
p1.name="Wu";
p1.age=;
var p1Keys=Object,keys(p1);
alert(p1Keys);//"name,age"
Object.getOwnPropertyNames()傳回所有執行個體屬性
var keys=Object.getOwnPropertyNames(Person.prototype);
alert(keys);//“constructor,name,age,job,sayName"
更簡單的原型文法:
Person.prototype={}
function Person(){}
Person.prototype={
name:"Chiaki",
age:,
job:"Software Engineer",
sayName:function(){
alert(this.name);
}
};
上例中,我們将Person.prototype設定為等于一個以對象字面量形式建立的新對象,此時constructor屬性不再指向Person.(原因:Person.prototype={}本質上完全重寫了建立構造函數Person時預設的prototype對象,是以constructor屬性就變成了新對象的constructor屬性【指向Object構造函數】)。
此時如果constructor的值很重要時可特意将它設定回适當的值,如下:
funtion Person(){
}
Person.prototype={
constructor:Person,//重設constructor屬性将導緻它的[[enumerable]]特性被設定為true,可以通過Object.defineProperty()修改成不可枚舉的
name:"Chiaki",
age:,
job:"Software Engineer",
sayName:function(){
alert(this.name);
}
};
原型的動态性:由于在原型中查找值的過程是一次搜尋,是以我們對原型對象所做的任何修改都能夠立即從執行個體上反應出——即使先建立了執行個體後修改原型也照樣如此。
var friend=new Person();
Person.prototype.sayHi=function(){
alert("hi");
}
friend.sayHi();//"hi"
執行個體和原型時間的連接配接是一個指針而非一個副本,是以可以随時為原型添加屬性和方法,并且修改能夠立即在所有對象執行個體中反應出來。但是如果是重寫整個原型對象,将會切斷構造函數和最初原型之間的聯系。執行個體中的指針僅指向原型而非構造函數。
原型對象的問題:1、省略了為構造函數傳遞初始化參數的環節,結果所有執行個體在預設情況下都将取得相同的屬性值;2、原型對象具有共享的本性,對于包含引用類型值的屬性,如果一個原型對象建立的兩個執行個體其中一個修改了引用類型的屬性值,将導緻另一個執行個體的該引用類型的屬性值也被改變。
function Person(){}
Person.prototype={
constructor=Person,
friends:["lola","cherry"],
}
var person1=new Person();
var person2=new Person();
person1.friends.push("van");
alert(person1.friends);//lala,cherry,van
alert(person2.friends);//lola,cherry,van
alert(person1.friends==person2.friends)//true
組合使用構造函數模式和原型模式
構造函數模式用于定義執行個體屬性,原型模式用于定義方法和共享屬性。
function Person(name,age,job){
this.name=name;
this.age=age;
this.job=job;
this.friends=["lola","cherry"];
}
Person.prototype={
constructor:Person,
sayName:function(){
alert(this.name);
}
}
var person1=new Person("Chiaki",,"Software Engineer");
var person2=new Person("Wu",,"Student");
person1.friends.push("Van");
alert(person1.friends);//lola,cherry,Van
alert(person2.friends);//lola,cherry
alert(person1.friends==person2.friends);//false//friends是構造函數中定義的執行個體屬性
alert(person1.sayName==person2.sayName);//trues//sayName()是原型中定義的,具有共享性
動态原型模式
把所有資訊封裝在構造函數中,而通過在構造函數中初始化原型又保持了同時使用構造函數和原型的優點。即通過檢查某個應該村咋的方法是否有效來決定是否需要初始化原型。
function Person(name,age,job){
//屬性
this.name=name;
this.age=age;
this.job=job;
//方法初始化之後應該存在的任何屬性的方法
if(typeof this.sayName!="function"){//檢查
Person.prototype.sayName=function(){
alert(this.name);
}
}
}
var friend=new Person("Chiaki",,"Software Engineer");
friend.sayName();
寄生式構造函數模式
建立一個函數以封裝建立對象的代碼,然後傳回新建立的對象。
function Person(name,age,job){
var o=new Object();
o.name=name;
o.age=age;
o.job=job;
o.sayName=function(){
alert(this.name);
};
return o;
}
var friend=new Person("Chiaki",,"Software Engineer");
friend.sayName();//Chiaki
構造函數在不傳回值的情況下預設會傳回新對象執行個體,而通過在構造函數的末尾添加一個return語句,可以重寫調用構造函數時傳回的值。構造函數傳回的對象與構造函數或者構造函數的原型屬性之間沒有關系。
穩妥構造函數模式
穩妥對象:即沒有公共屬性,且方法也不用this的對象。适用于安全的環境中(該環境下禁用this和new)或者在在防止資料被其他應用程式改動時使用。
function Person(name,age,job){
//建立要傳回的對象
var o=new Object();
//可以在這裡定義私有變量和函數
//添加方法
o.sayName=function(){
alert(name);
}
//傳回對象
return o;
}
這種模式下建立的對象在,除使用sayName方法外,沒辦法通路name值。
【參考自《javascript進階程式設計(第三版)》】