對象字面量
var person = {
name:"aa",
age:20,
job:"student",
sayName:function(){
alert(this.name);
}
}
工廠模式
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("aa",20,"student");
var person2 = createPerson("bb",22,"teacher");
構(gòu)造函數(shù)模式
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){
alert(this.name);
};
}
var person1 = new Person("aa",20,"student");
var person2 = new Person("bb",22,"teacher");
構(gòu)造函數(shù)始終應(yīng)以大寫字母開頭徽曲,非構(gòu)造函數(shù)則以小寫字母開頭。
要?jiǎng)?chuàng)建Person的新實(shí)例,必須用new操作符朋譬。
person1和person2都有一個(gè)constructor(構(gòu)造函數(shù))屬性,該屬性指向Person兴垦。
person1.constructor == Person; //true
優(yōu)于工廠模式:可以將他的實(shí)例表示為一種特定的類型而非只是Object徙赢。
person1 instanceof Person //true
構(gòu)造函數(shù)與其它函數(shù)的區(qū)別在于調(diào)用方式不同。任何函數(shù)通過new操作符調(diào)用都會(huì)作為構(gòu)造函數(shù)探越,反之不通過new操作符調(diào)用那么就是普通函數(shù)狡赐。
//當(dāng)做構(gòu)造函數(shù)
var person1 = new Person("aa",20,"student");
person1.sayName(); //aa
//作為普通函數(shù)
Person("ee",20,"student");//添加到window
window.sayName();//ee
//在另一個(gè)對象的作用域中調(diào)用
var o = new Object();
Person.call(o,"cc",34,"mother");
o.sayName(); //cc
作為普通函數(shù)調(diào)用時(shí),在全局作用域中調(diào)用的函數(shù)this指向window對象钦幔。
構(gòu)造函數(shù)缺點(diǎn):每個(gè)方法都會(huì)在每個(gè)實(shí)例上重新創(chuàng)造一遍枕屉。不同實(shí)例上的同名函數(shù)是不相等的。
原型模式
原型對象:包含所有實(shí)例共享的屬性和方法鲤氢。
function Person(){
}
Person.prototype.name = "aa";
Person.prototype.age = 20;
Person.prototype.job = "student";
Person.prototype.sayName = function(){
alert(this.name);
};
var person1 = new Person();
person1.sayName(); //"aa"
var person2 = new Person();
person2.sayName(); //"aa"
person1.sayName == person2.sayName; //true
上例中所有實(shí)例訪問同一組屬性與方法搀擂。
檢測是否是實(shí)例的原型對象:
Person.prototype.isPrototypeOf(person1); //true
Object.getPrototypeOf(person1) == Person.prototype; //true
Object.getPrototypeOf方便地取得一個(gè)對象的原型西潘。
搜索屬性:
當(dāng)代碼讀取某個(gè)對象的某個(gè)屬性時(shí),都會(huì)執(zhí)行一次搜索哨颂。
對象實(shí)例可以訪問原型中的值喷市,但不能重寫。若實(shí)例中添加了一個(gè)與原型中某一個(gè)屬性同名的屬性威恼,則該屬性會(huì)屏蔽原型中的同名屬性:
function Person(){
}
Person.prototype.name = "aa";
Person.prototype.age = 20;
Person.prototype.job = "student";
Person.prototype.sayName = function(){
alert(this.name);
};
var person1 = new Person();
person1.sayName(); //"aa"
var person2 = new Person();
person2.sayName(); //"aa"
person1.name = "bb" ;
person1.name; //"bb"---來自實(shí)例
person2.name; //"aa"---來自原型
使用delete操作符可以刪除實(shí)例屬性:
person1.name = "bb" ;
person1.name; //"bb"---來自實(shí)例
person2.name; //"aa"---來自原型
delete person1.name;
person1.name; //"aa" ---來自原型
hasOwnProperty:檢測一個(gè)屬性存在于實(shí)例還是存在于原型品姓。
person1.hasOwnProperty("name"); //false
person1.name = "bb" ;
person1.name; //"bb"---來自實(shí)例
person1.hasOwnProperty("name"); //true
delete person1.name;
person1.name; //"aa" ---來自原型
person1.hasOwnProperty("name"); //false
單獨(dú)使用in操作符:對象能訪問給定屬性時(shí)返回true,無論此屬性是來自實(shí)例還是原型箫措。
function Person(){
}
Person.prototype.name = "aa";
Person.prototype.age = 20;
Person.prototype.job = "student";
Person.prototype.sayName = function(){
alert(this.name);
};
var person1 = new Person();
var person2 = new Person();
person1.hasOwnProperty("name"); //false
"name" in person1 //true
person1.name = "bb" ;
person1.name; //"bb"---來自實(shí)例
person1.hasOwnProperty("name"); //true
"name" in person1 //true
因此利用hasOwnProperty與in可以判斷一個(gè)屬性是來自實(shí)例還是原型腹备。
function hasPrototypeProperty(object,name){
return !object.hasOwnPeoperty(name)&&(name in object);
}
使用for-in時(shí),返回的是對象能訪問的蒂破,可枚舉的屬性馏谨,其中包括來自實(shí)例的也包括來自原型的。
Object.keys( )方法可返回對象上所有可枚舉的實(shí)例屬性:
function Person(){
}
Person.prototype.name = "aa";
Person.prototype.age = 20;
Person.prototype.job = "student";
Person.prototype.sayName = function(){
alert(this.name);
};
var keys = Object.keys(Person.prototype); //["name", "age", "job", "sayName"]
var person1 = new Person();
var keys = Object.keys(person1 );//[]
person1.name = "bb";
person1.age = 23;
var keys = Object.keys(person1 );//["name", "age"]
獲取所有實(shí)例屬性而無論其是否可枚舉:Object.getOwnPropertyNames( )
var keys = Object.getOwnPropertyNames(Person.prototype);//["constructor", "name", "age", "job", "sayName"]
結(jié)果中包含了不可枚舉的constructor屬性附迷。
Object.keys與Object.getOwnPropertyNames均可替代for-in惧互。
更簡單的原型語法
function Person(){
}
Person.prototype = {
name:"aa",
age:20,
job:"student",
sayName:function(){
alert(this.name);
}
}
但是在這種語法下,本質(zhì)上是完全重寫了默認(rèn)的prototype對象喇伯,因此constructor屬性也就不再指向Person而是Object喊儡。
若constructor屬性十分重要,則可以像下面這樣:
function Person(){
}
Person.prototype = {
constructor:Person,
name:"aa",
age:20,
job:"student",
sayName:function(){
alert(this.name);
}
}
而此時(shí)由用戶重設(shè)的constructor屬性已經(jīng)變成了可枚舉的了稻据。
由于在原型中查找值的過程是一次搜索艾猜,因此對原型對象所做的任何修改都能夠立即從實(shí)例上反映出來,即使是先創(chuàng)建實(shí)例后修改原型捻悯。
function Person(){}
var friend = new Person();
Person.prototype.sayHi =function(){
alert("Hi");
}
friend.sayHi(); //Hi
由于實(shí)例和原型間是松散連接關(guān)系匆赃,其間不過是一個(gè)指針,因此通過上面的搜索屬性就可以在原型中找到新的方法今缚。
但是如果重寫整個(gè)原型對象的話(如上面的更簡單的原型語法)算柳,由于調(diào)用構(gòu)造函數(shù)時(shí)會(huì)為實(shí)例添加一個(gè)指向最初原型的指針,如果把原型指向另一個(gè)對象就等于切斷了新原型與已經(jīng)存在的實(shí)例之間聯(lián)系姓言,也切斷了構(gòu)造函數(shù)與舊原型的聯(lián)系瞬项。已經(jīng)存在的實(shí)例引用的仍是最初的原型。
function Person(){}
var friend = new Person();
Person.prototype = {
constructor:Person,
name:"aa",
age:20,
job:"student",
sayName:function(){
alert(this.name);
}
}
friend.sayName(); //error
下圖展示了上列過程:
原生對象如Object何荚,Array囱淋,String等都是在其構(gòu)造函數(shù)的原型上定義了方法。我們也可以用此方法定義新方法:
String.prototype.startWith = function(text){
return this.indexOf(text)==0;
}
var msg = "hello world";
alert(msg.startWith("hello")); //true
原型對象也有缺點(diǎn):
1.省略了為構(gòu)造函數(shù)傳遞初始化參數(shù)這一環(huán)節(jié)餐塘,結(jié)果所有實(shí)例在默認(rèn)情況下都會(huì)取得相同的屬性值妥衣。
2.原型中的屬性是被多實(shí)例共享的,對于基本值屬性還好,因?yàn)榭梢酝貙懗屏郏珜τ诎妙愋椭档膶傩詠碚f涮较,就會(huì)有如下問題:
function Person(){
}
Person.prototype = {
constructor:Person,
name:"aa",
age:20,
job:"student",
friends:["bb","cc"],
sayName:function(){
alert(this.name);
}
}
var person1 = new Person();
var person2 = new Person();
person1.friends.push("dd");
person1.friends;//"bb,cc,dd"
person2.friends;//"bb,cc,dd"
person1.friends == person2.friends; //true
組合使用構(gòu)造函數(shù)模式與原型模式
創(chuàng)建自定義類型的最常見方式,構(gòu)造函數(shù)用于定義實(shí)例屬性冈止,原型用于定義方法和共享的屬性狂票。結(jié)果,每個(gè)實(shí)例都會(huì)有自己的一份實(shí)例屬性同時(shí)又共享者對方法的引用熙暴,最大限度地節(jié)省了內(nèi)存闺属。
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.friends = ["bb","cc"];
}
Person.prototype = {
constructor:Person,
sayName:function(){
alert(this.name);
}
}
var person1 = new Person("alice",30,"students");
var person2 = new Person("bob",34,"ceo");
person1.friends.push("dd");
person1.friends;//"bb,cc,dd"
person2.friends;//"bb,cc"
person1.friends == person2.friends; //false
person1.sayName == person2.sayName; //true
動(dòng)態(tài)原型模式
把所有信息都封裝在構(gòu)造函數(shù)中,通過在構(gòu)造函數(shù)中初始化原型周霉,同時(shí)保證了構(gòu)造函數(shù)和原型的優(yōu)點(diǎn)掂器。
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("alice",30,"students");
friend.sayName(); //alice
if檢測那段代碼只會(huì)在初次調(diào)用構(gòu)造函數(shù)時(shí)執(zhí)行,此后原型已經(jīng)完成初始化俱箱,之后調(diào)用構(gòu)造函數(shù)創(chuàng)建的實(shí)例都會(huì)有sayname屬性国瓮,不會(huì)走if里面的語句。if語句檢查的可以是初始化之后應(yīng)該存在的屬性或方法狞谱。
使用動(dòng)態(tài)函數(shù)模型時(shí)不能使用對象字面量重寫原型乃摹,否則會(huì)切斷現(xiàn)有實(shí)例與新原型之間的聯(lián)系。