建立多個相似對象時使用的方法:
1. 工廠模式
//工廠模式
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 person=createPerson("Jim",22,"Doctor");
- 缺點:工廠模式雖然解決了建立多個相似對象的問題,但卻沒有解決對象識别的問題(怎樣知道一個對象的類型)。
2.構造函數模式
//構造函數模式
function Person(name,age,job){
this.name=name;
this.age=age;
this.job=job;
this.sayName=function(){
alert(this.name);
}
}
var person=new Person("Jim",22,"Doctor");
-
構造函數模式相對于工廠模式有幾個顯著的差別
(1) 沒有顯式地建立對象
(2)直接将屬性和方法賦給了this對象
(3)沒有return語句
(4)函數名的首字母大寫(Person和createPerson):按照慣例,構造函數始終都是以大寫字母開頭,而非構造函數則應該以小寫字母開頭
-
解決對象識别問題
(1)constructor
上面的例子使person儲存這個Person的示執行個體,是以有了一個constructor(構造函數)屬性,該屬性指向Person
alert( person.constructor == Person ); //true
對象的constructor屬性最初是用來辨別對象類型的,但是檢測對象類型還是instanceof操作符更加可靠
(2) instanceof
alert( person instanceof Object ); //true
這個例子裡建立的對象既是Object執行個體又是Person的執行個體alert( person instanceof Person); //true
-
new Person()這條語句經曆了什麼
(a)建立一個新對象
(b)将構造函數的作用域賦給新對象(是以this就指向了這個新對象)
©執行構造函數中的代碼(為這個新對象添加屬性)
(d)傳回新對象
-
構造函數的問題
ECMAScript中的函數是對象,是以每定義一個函數,也就是執行個體化了一個對象。而構造函數在每次建立的時候都會完成同樣任務的function執行個體(sayName),這是很沒有必要的。是以可以把函數定義轉移到構造函數外部來解決這個問題。
function Person(name,age,job){
this.name=name;
this.age=age;
this.job=job;
this.sayName=sayName;
}
function sayName(){
alert(this.name);
}
這樣做确實解決了兩個函數做同一件事的問題,可是新問題是:(a)全局函數不再“全局”了,而隻能由Person對象調用(b)沒有了“封裝性”。如果對象需要定義很多方法,那麼就要定義很多個的全局函數,于是我們的自定義的引用類型就絲毫沒有封裝性可言了。而接下來的原型模式就可以解決這個問題
3.原型模式
//原型模式
function Person(){}
Person.prototype.name="Jim";
Person.prototype.age=22;
Person.protoType.job="Doctor";
Person.protoType.sayName=function(){
alert(this.name);
}
var person=new Person();
person.sayName(); //Jim
-
原型模式的好處
可以讓所有對象執行個體共享它所包含的屬性和方法。換句話說,不必在構造函數中定義對象執行個體的資訊,而是可以讓這些資訊直接添加到原型對象中。
-
原型對象是什麼
(1)無論什麼時候,隻要建立了一個新函數,就會根據一組特定的規則為該函數建立一個prototype屬性,這個屬性指向函數的原型對象。在預設情況下,所有的原型對象都會自動獲得一個constructor(構造函數)的屬性,這個屬性包含一個指向prototype屬性所在的函數的指針, 如下:
(2)在我們調用alert( Person.prototype.constructor==Person ); //true
person.sayName();
的時候,會先後執行兩次搜尋,先找執行個體再找原型。
首先解釋器會問:“執行個體person有sayName屬性嗎?”
答曰:“沒有”
然後繼續搜尋,再問“person的原型裡有sayName屬性嗎?”
答曰:“有”
于是他就讀取那個儲存在原型對象中的函數
(3)當給person主動添加屬性時,就會屏蔽掉原型的屬性(但不會被替代,如果其他Person對象調用name屬性,還會預設為Jim),如:
function Person(){}
Person.prototype.name="Jim";
Person.prototype.age=22;
Person.protoType.job="Doctor";
Person.protoType.sayName=function(){
alert(this.name);
}
var person=new Person();
person.name="Tom";
alert(person.name); //Tom
此時person的name被一個新值給屏蔽了
(4)使用delete操作符删除 執行個體 屬性
person.name="Tom";
alert(person.name); //Tom
delete person.name;
alert(person.name); //Jim
(5)hasOwnProperty()方法檢測一個屬性時存在于執行個體中還是原型中
var person=new Person();
alert(person.hasOwnProperty("name")) //false:存在原型中
person.name="Tom";
alert(person.hasOwnProperty("name")) //true:存在對象執行個體中
(6)判斷對象中傳入的參數類型(隻要有prototype屬性的對象都能用)Object.prototype.toString.call(options)
使用方法:https://www.cnblogs.com/wyaocn/p/5796142.html
例如判斷傳入的參數是否為對象
function Person(name) {
alert(Object.prototype.toString.call(name))
}
var Tim={
age:11,
job:"student",
}
var person=new Person(Tim); //[object,object]
-
原型與in操作符
in操作符通過對象通路對象中的屬性時,傳回true ,無論該屬性是存在于執行個體中還是原型中
var person=new Person;
alert( "name" in person ); //true
person.name="Tom"; //将name寫在執行個體中
alert( "name" in person ); //true
是以想要确定該屬性是存在于原型中還是執行個體中,隻需要同時使用hasOwbProperty和in操作符
function hasPrototypeProperty(object,name){
return !object.hasOwnProperty(name)&&(name in object); //true:在原型中,false:在執行個體中
}
for-in循環,所有開發人員定義的屬性都是可枚舉的(IE8-除外),是以所有屬性都可以(執行個體屬性和原型屬性)使用for-in循環
for(var prop in person){
if(prop=="name"){
alert("Found name!") ; //IE除外
}
}
IE8-的解決辦法見《JavaScript進階程式設計》(第三版)——人民郵電出版社,P153~154
- 更簡單的原型文法
function Person(){}
Person.prototype={
name:"Jim",
age:22,
job:"Doctor",
sayName:function(){
alert(this.name);
}
}
注意,此時的constructor屬性不再指向Person了。前面介紹過,每建立一個函數,會同時建立它的prototype對象,這個對象會自動擷取constructor屬性,這裡本質上重寫了預設的prototype對象,此時constructor屬性指向Object構造函數,經管如此,instanceof操作符還能傳回正确的結果。
如果此時constructor的值很重要,可以在person.prototype中設定它,但會導緻constructor的預設不可枚舉特性被設定為可枚舉。
function Person(){}
Person.prototype={
constructor:Person,
name:"Jim",
age:22,
job:"Doctor",
sayName:function(){
alert(this.name);
}
}
如果要相容ES5,可以試一下object.definProperty()
function Person(){}
Person.prototype={
name:"Jim",
age:22,
job:"Doctor",
sayName:function(){
alert(this.name);
}
};
//重設構造函數,隻适用于ES5相容的浏覽器
Object.definPrototype(Person.prototype,"constructor",{
enumerable:false, //不可枚舉
value:Person,
})
-
原型對象的問題
對于包含引用類型值的屬性來說,原型模式最大的問題——“共享”性,就非常突出了。
看下面的例子
function Person(){}
Person.prototype={
friends:["Amy","Sam"],
};
var person1=new Person();
var person2=new Person();
person1.friends.push("Van");
alert(person1.friends); //"Amy,Sam,Van"
alert(person2.friends); //"Amy,Sam,Van"
當沒有對Person的friends指派時,直接調用原型屬性并修改,就會直接導緻原型修改。
而這個問題證明我們很少有人單獨使用原型模式的問題所在。解決辦法看下面。
4.組合使用構造函數模式和原型模式
建立自定義類型最常見的方式,就是組合構造函數模式和原型模式。構造函數模式用于定義執行個體屬性,而原型模式用于定義方法和共享屬性
//組合使用構造模式和原型模式
function Person(name,age,job){
this.name=name;
this.age=age;
this.job=job;
this.friends=["Amy","Tom"];
}
Person.prototype={
constructor:Person,
sayName:function(){
alert(this.name);
}
}
var person1=new Person();
var person2=new Person();
person1.friends.push("Van");
alert(person1.friends); //Amy,Tom,Van
alert(person2.friends); //Amy,Tom
總結
在沒有類的情況下,可以使用下列模式建立對象:
(1)工廠模式:在函數裡面建立對象,并添加屬性和方法。但沒有解決對象識别問題,最後被構造函數模式所取代。
(2)構造函數模式:可以像建立内置對象執行個體一樣使用new操作符。但沒函數不局限于任何對象,是以沒有了封裝性。
(3)原型模式:使用構造函數的prototype屬性指定應該共享的屬性和方法。但單獨使用原型模式的話,就會使不需要共享的屬性和方法被共享
(4)組合使用構造函數模式和原型模式:使用構造函數定義執行個體屬性,使用原型定義共享的屬性和方法
-----------------------------------《JavaScript進階程式設計(第三版)》讀書筆記--------------------------------------------------------
擴充:
5. JSON格式的JS對象
-
基本寫法
遵照JSON原則 , PI , area相當于關鍵字,必須要打引号(ps:據說這是現在最流行的寫法之一,至于具體原因,以後弄懂了會補充)
var Circle={
"PI":3.14159,
"area":function(r){
return this.PI * r * r;
}
};
alert( Circle.area(1.0) );
- 缺點:隻用來初始化對象
- 優點:可以了解成封包, 且非常靈活,本身就是一種json格式,是以友善應用于json格式傳遞環境下