創(chuàng)建對(duì)象
Object構(gòu)造函數(shù)或?qū)ο笞置媪縿?chuàng)建對(duì)象的缺點(diǎn)是使用同一個(gè)接口創(chuàng)建很多對(duì)象。
1.工廠模式
工廠模式抽象了創(chuàng)建具體對(duì)象的過(guò)程。用函數(shù)來(lái)封裝以特定接口創(chuàng)建對(duì)象。
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("Icey",25,"Softerware Engineer");
var person2 = createPerson("Root",21,"Softerware Engineer");
2.構(gòu)造函數(shù)模式
構(gòu)造函數(shù)可以用來(lái)創(chuàng)建特定類型的對(duì)象。
function Person(name,age,job) {
this.name = name;
this.age = age;
this.job = job;
ths.sayName = function () {
alert(this.name);
};
}
var person1 = new Person("Icey",25,"Softerware Engineer");
var person2 = new Person("Root",21,"Softerware Engineer");
- 沒(méi)有顯示的創(chuàng)建對(duì)象;
- 直接將屬性和方法賦給了this對(duì)象;
- 沒(méi)有return語(yǔ)句。
按照慣例,構(gòu)造函數(shù)始終都應(yīng)該以一個(gè)大寫(xiě)字母開(kāi)頭。構(gòu)造函數(shù)本身也是函數(shù),只不過(guò)是用來(lái)創(chuàng)建對(duì)象而已。
要?jiǎng)?chuàng)建Person的新實(shí)例,必須使用new操作符:
- (1) 創(chuàng)建一個(gè)新對(duì)象
- (2) 將構(gòu)造函數(shù)的作用域賦給新對(duì)象(因此this就指向了這個(gè)新對(duì)象)
- (3) 執(zhí)行構(gòu)造函數(shù)中的代碼(為這個(gè)新對(duì)象添加屬性)
- (4) 返回新對(duì)象
person1和person2分別保存這Person的一個(gè)不同的實(shí)例,這兩個(gè)對(duì)象都有一個(gè)constructor(構(gòu)造函數(shù))屬性,該屬性指向Person。
alert(person1.constructor == Person); //true
alert(person2.constructor == Person); //true
constructor屬性用來(lái)表示對(duì)象類型,檢測(cè)對(duì)象類型用instanceof操作符更可靠。
alert(person1 instanceof Object);//true
alert(person1 instanceof Person);//true
alert(person2 instanceof Object);//true
alert(person2 instanceof Person);//true
創(chuàng)建自定義的構(gòu)造函數(shù)意味著將來(lái)可以將它的實(shí)例標(biāo)識(shí)為一種特定的類型;這正式構(gòu)造函數(shù)模式勝過(guò)工廠模式的地方。
以這種方式定義的構(gòu)造函數(shù)是定義在Global對(duì)象中的。
1.將構(gòu)造函數(shù)當(dāng)作函數(shù)
構(gòu)造函數(shù)與其他函數(shù)的唯一區(qū)別,就在于調(diào)用它們的方式。任何函數(shù),只有通過(guò)new操作符來(lái)調(diào)用,那它就可以作為構(gòu)造函數(shù)。
//當(dāng)作構(gòu)造函數(shù)使用
var person = new Person("Icey",25,"Softerware Engineer");
person.sayName(); //"Icey"
//作為普通函數(shù)調(diào)用
Person("Root",24,"Softerware Engineer"); //添加到window
window.sayName(); //"Root"
//在另一個(gè)對(duì)象的作用域中調(diào)用
var o = new Object();
Person.call(o,"Icey",25,"Softerware Engineer");
o.sayName(); //"Icey"
2.構(gòu)造函數(shù)問(wèn)題
構(gòu)造函數(shù)的主要問(wèn)題,就是每個(gè)方法都要在每個(gè)實(shí)例上重新創(chuàng)建一遍。
ECMAScript中的函數(shù)是對(duì)象,因此每定義一個(gè)函數(shù),也就是實(shí)例化了一個(gè)對(duì)象。
function Person(name,age,job) {
this.name = name;
this.age = age;
this.job = job;
//與聲明函數(shù)在邏輯上是等價(jià)的
this.sayName = new Function("alert(this.name)");
}
每個(gè)Person實(shí)例都包含一個(gè)不同的Function實(shí)例。以這種方式創(chuàng)建函數(shù),會(huì)導(dǎo)致不同的作用域鏈和標(biāo)識(shí)符解析,但創(chuàng)建Function的機(jī)制任然是相同的。不同實(shí)例上的同名函數(shù)是不想等的。
alert(person1.sayName == person2.sayName); //false
可以通過(guò)把函數(shù)定義轉(zhuǎn)移到構(gòu)造函數(shù)外面來(lái)解決這個(gè)問(wèn)題。
function Person(name,age,job) {
this.name = name;
this.age = age;
this.job = job;
ths.sayName = sayName;
}
function sayName() {
alert(this.name);
}
var person1 = Person("Icey",25,"Softerware Engineer");
var person2 = Person("Root",21,"Softerware Engineer");
sayName包含的是一個(gè)指向函數(shù)的指針,因此person1和person2對(duì)象共享了全局作用域中定義的同一個(gè)sanName()函數(shù)。可是這樣,自定義的引用類型就沒(méi)有封裝性可言可,通過(guò)原型模式可以解決。
3.原型模式
創(chuàng)建的每個(gè)函數(shù)都有一個(gè)prototype(原型)屬性,這個(gè)屬性是一個(gè)指針,指向一個(gè)對(duì)象,這個(gè)對(duì)象的用途是包含可以有特定類型的所有實(shí)例共享的屬性和方法。
prototype就是通過(guò)調(diào)用構(gòu)造函數(shù)而創(chuàng)建的那個(gè)對(duì)象實(shí)例的原型對(duì)象。使用原型對(duì)象的好處是可以讓所有對(duì)象實(shí)例共享它所包含的屬性和方法。
function Person() {
}
Person.prototype.name = "Icey";
Person.prototype.age = 25;
Person.prototype.job = "Softerware Engineer";
Person.prototype.sayName = function () {
alert(this.name);
};
var person1 = new Person();
person1.sayName(); //"Icey"
var person2 = new Person();
person2.sayName(); //"Icey"
alert(person1.sayName == person2.sayName); //true
1.理解原型對(duì)象
無(wú)論什么時(shí)候,只要?jiǎng)?chuàng)建了一個(gè)新函數(shù),就會(huì)根據(jù)一組特定的規(guī)則為該函數(shù)創(chuàng)建一個(gè)prototype屬性,這個(gè)屬性指向函數(shù)的原型對(duì)象。在默認(rèn)情況下,所有原型對(duì)象會(huì)自動(dòng)獲得一個(gè)constructor(構(gòu)造函數(shù))屬性,這個(gè)屬性是一個(gè)指向prototype屬性所在函數(shù)的指針。Person.prototype.constructor指向Person。通過(guò)構(gòu)造函數(shù),可以繼續(xù)為原型對(duì)象添加屬性和方法。
創(chuàng)建了自定義的構(gòu)造函數(shù)之后,其原型對(duì)象只會(huì)取得constructor屬性,至于其他方法都是從Object繼承而來(lái)。當(dāng)調(diào)用構(gòu)造函數(shù)創(chuàng)建一個(gè)新實(shí)例后,該實(shí)例的內(nèi)部將包含一個(gè)指針(內(nèi)部屬性),指向構(gòu)造函數(shù)的原型對(duì)象。ECMA-262第5版管這個(gè)指針叫[[Prototype]]。雖然腳本中沒(méi)喲標(biāo)準(zhǔn)方式訪問(wèn)[[Prototype]],但Firefox、Safari和Chrome在每個(gè)對(duì)象上都支持一個(gè)屬性__proto__。這個(gè)連接存在與實(shí)例與構(gòu)造函數(shù)的原型對(duì)象之間,而不是存在與實(shí)例與構(gòu)造函數(shù)之間。
雖然所有實(shí)現(xiàn)中都無(wú)法訪問(wèn)到[[Prototype]],但可以通過(guò)isPrototypeOf()方法來(lái)確定對(duì)象之間是否存在這種關(guān)系。
alert(Person.prototype.isPrototypeOf(person1));//true
alert(Person.prototype.isPrototypeOf(person2));//true
ECMAScript5增加了,Object.getPrototypeOf(),這個(gè)方法返回[[Prototype]]的值。
alert(Object.getPrototypeOf(person1) == Person.prototype);//true
alert(Object.getPrototypeOf(person).name);//"Icey"
這在利用原型實(shí)現(xiàn)繼承的情況下非常重要。
每當(dāng)代碼讀取某個(gè)對(duì)象的屬性是,都會(huì)執(zhí)行一次搜索,目標(biāo)是具有給定名字的屬性。搜索首先從對(duì)象實(shí)例本身開(kāi)始,如果實(shí)例中找到了,具有給定名字的屬性,則返回該屬性的值,如果沒(méi)有找到,則繼續(xù)搜索指針指向的原型對(duì)象,在原型對(duì)象中查找具有給定名字的屬性。如果原型對(duì)象中找到了,則返回該屬性的值。
原型最初值包含constructor屬性。
function Person() {
}
Person.prototype.name = "Icey";
Person.prototype.age = 25;
Person.prototype.job = "Softerware Engineer";
Person.prototype.sayName = function () {
alert(this.name);
};
var person1 = new Person();
var person2 = new Person();
person1.name = "Greg";
person1.sayName(); //"Greg" 來(lái)自實(shí)例
person2.sayName(); //"Icey" 來(lái)自原型
當(dāng)為對(duì)象實(shí)例添加一個(gè)屬性時(shí),這個(gè)屬性就會(huì)屏蔽原型對(duì)象中保存的同名屬性。
function Person() {
}
Person.prototype.name = "Icey";
Person.prototype.age = 25;
Person.prototype.job = "Softerware Engineer";
Person.prototype.sayName = function () {
alert(this.name);
};
var person1 = new Person();
var person2 = new Person();
person1.name = "Greg";
person1.sayName(); //"Greg" 來(lái)自實(shí)例
person2.sayName(); //"Icey" 來(lái)自原型、
delete person1.name;
alert(person1.name); //"Icey" 來(lái)自原型
使用delete操作符可以完全刪除實(shí)例屬性,能夠重新訪問(wèn)原型中的屬性。
hsaOwnProperty()方法可以檢測(cè)屬性存在與實(shí)例中,還是存在與原型中。
function Person() {
}
Person.prototype.name = "Icey";
Person.prototype.age = 25;
Person.prototype.job = "Softerware Engineer";
Person.prototype.sayName = function () {
alert(this.name);
};
var person1 = new Person();
var person2 = new Person();
alert(person1.hasOwnProperty("name")); //false
person1.name = "Greg";
alert(person1.name);//"Greg" 來(lái)自實(shí)例
alert(person1.hasOwnProperty("name")); //true
alert(person1.name); //"Icey" 來(lái)自原型
alert(person1.hasOwnProperty("name")); //false
delete person1.name;
alert(person1.name);//"Icey" 來(lái)自原型
alert(person1.hasOwnProperty("name")); //false
2.原型與in操作符
有兩種方式使用in操作符:?jiǎn)为?dú)使用和在for-in循環(huán)中使用。在單獨(dú)使用時(shí),in操作符會(huì)在通過(guò)對(duì)象能夠訪問(wèn)給定屬性是返回true,無(wú)論改屬性存在于實(shí)例中還是原型中。
function Person() {
}
Person.prototype.name = "Icey";
Person.prototype.age = 25;
Person.prototype.job = "Softerware Engineer";
Person.prototype.sayName = function () {
alert(this.name);
};
var person1 = new Person();
var person2 = new Person();
alert(person1.hasOwnProperty("name")); //false
alert("name" in person1);//true
person1.name = "Greg";
alert(person1.name);//"Greg" 來(lái)自實(shí)例
alert(person1.hasOwnProperty("name")); //true
alert("name" in person1);//true
alert(person1.name); //"Icey" 來(lái)自原型
alert(person1.hasOwnProperty("name")); //false
alert("name" in person1);//true
delete person1.name;
alert(person1.name);//"Icey" 來(lái)自原型
alert(person1.hasOwnProperty("name")); //false
alert("name" in person1);//true
···
確定屬性是原型中的屬性
function hasPrototypeProperty(object,name) {
return !object.hasOwnProperty(name) && (name in object);
}
function Person() {
}
Person.prototype.name = "Icey";
Person.prototype.age = 25;
Person.prototype.job = "Softerware Engineer";
Person.prototype.sayName = function () {
alert(this.name);
};
var person = new Person();
alert(hasPrototypeProperty(person,"name")); //true
person.name = "Greg";
alert(hasPrototypeProperty(person,"name")); //false
使用for-in循環(huán)時(shí),返回的是所有能夠通過(guò)對(duì)象訪問(wèn)的、可枚舉的屬性,既包括存在與實(shí)例中的屬性,也包括存在于原型中的屬性。屏蔽了原型中不可枚舉的屬性([[Enumerable]]標(biāo)記為false的屬性)的實(shí)例也會(huì)在for-in循環(huán)中返回。
Object.keys()方法接收一個(gè)對(duì)象作為參數(shù),返回一個(gè)包含所有可枚舉屬性的字符串數(shù)組。
function Person() {
}
Person.prototype.name = "Icey";
Person.prototype.age = 25;
Person.prototype.job = "Softerware Engineer";
Person.prototype.sayName = function () {
alert(this.name);
};
var keys = Object.keys(Person.prototype);
alert(keys);//"name,age,job,sayName"
var p1 = new Person();
p1.name = "Rob";
p1.age = 22;
var p1keys = Object.keys(p1);
alert(p1keys);//"name,age"
Object.getOwnPropertyNames()可以得到所有實(shí)例屬性。
var keys = Object.getOwnPropertyNames(Person.prototype);
var keys = Object.getOwnPropertyNames(Person.prototype);
alert(keys);//"constructor,name,age,job,sayName"
3.更簡(jiǎn)單的原型語(yǔ)法
用一個(gè)包含所有屬性和方法的對(duì)象字面量來(lái)重寫(xiě)整個(gè)原型對(duì)象。
function Person() {}
Person.prototype = {
name: "Icey",
age: 25,
job: "Softerware Engineer",
sayName: function() {
alert(this.name);
}
}
constructor屬性不再指向Person了。
var friend = new Person();
alert(friend instanceof Object);//true
alert(friend instanceof Person);//true
alert(friend.constructor == Person);//false
alert(friend.constructor == Object);//true
如果constructor真的很重要,可以特意將它設(shè)置回適當(dāng)?shù)闹怠?/p>
function Person() {}
Person.prototype = {
constructor: Person,
name: "Icey",
age: 25,
job: "Softerware Engineer",
sayName: function() {
alert(this.name);
}
}
這種方式重設(shè)constructor屬性會(huì)導(dǎo)致它的[[Enumerable]]特性被設(shè)置為true。默認(rèn)情況下,原生的construcor屬性是不可枚舉的。
//重設(shè)構(gòu)造函數(shù),只是用于ECMAScript兼容的瀏覽器
Object.defineProperty(Person.prototype,"constructor",{
enumerable: false,
value: Person
});
4.原型的動(dòng)態(tài)性
var friend = new Person();
Person.prototype.sayHi = function () {
alert('hi');
};
friend.sayHi();//"hi"
實(shí)例中的指針指向原型, 而不指向構(gòu)造函數(shù)。
function Person() {}
var friend = new Person();
Person.prototype = {
constructor: Person,
name: "Icey",
age: 25,
job: "Softerware Engineer",
sayName: function() {
alert(this.name);
}
};
friend.sayName();//error
先創(chuàng)建Person的一個(gè)實(shí)例,有重寫(xiě)了其原型對(duì)象。
重寫(xiě)原型對(duì)象切斷了現(xiàn)有原型與任何之前已經(jīng)存在的對(duì)象實(shí)例之間的聯(lián)系;它們引用的任然是最初的原型。
5.原生動(dòng)態(tài)原型
原生的引用類型也采用這種原型模式創(chuàng)建,所有原生引用類型(Object、Array、String等)都在其構(gòu)造函數(shù)的原型上定義了方法。
alert(typeof Array.prototype.sort);//"function"
alert(typeof String.prototype.substring);//"function"
可以通過(guò)原生對(duì)象的原型定義新方法。
String.prototype.satrtsWith = function (text) {
return this.indexOf(text) == 0;
};
var msg = "Hello world!";
alert(msg.satrtsWith("Hello"));//true
當(dāng)前環(huán)境中所有字符串都可以調(diào)用它。
6.原型對(duì)象的問(wèn)題
原型中的共享對(duì)于包含引用類型值的屬性來(lái)說(shuō),問(wèn)題比較突出。
function Person() {}
Person.prototype = {
constructor: Person,
name: "Icey",
age: 25,
job: "Softerware Engineer",
friends:["Shelby","Court"],
sayName: function() {
alert(this.name);
}
};
var person1 = new Person();
var person2 = new Person();
person1.friends.push("Van");
alert(person1.friends);//"Shelby,Court,Van"
alert(person2.friends);//"Shelby,Court,Van"
alert(person1.friends === person2.friends);//true
4.組合使用構(gòu)造函數(shù)模式和原型模式
創(chuàng)建自定義類型的最常見(jiàn)方式,就是組合使用構(gòu)造函數(shù)模式與原型模式。構(gòu)造函數(shù)模式用于定義實(shí)例屬性,原型模式由于定義方法和共享的屬性。
function Person(name,age,job) {
this.name = name;
this.age = age;
this.job = job;
this.friends = ["Shellby","Court"];
}
Person.prototype = {
constructor: Person,
sayName: function () {
alert(this.name);
}
};
var person1 = new Person("Icey",25,"Softerware Engineer");
var person2 = new Person("Root",22,"Softerware Engineer");
person1.friends.push("Van");
alert(person1.friends);//"Shelby,Count,Van"
alert(person2.friends);//"Shelby,Count"
alert(person1.friends === person2.friends);//false
alert(person1.sayName == person2.sayName); //true
5.動(dòng)態(tài)原型模式
通過(guò)檢測(cè)某個(gè)應(yīng)該存在的方法是否有效,來(lái)決定是否需要初始化原型。
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("Icey",25,"Softerware Engineer");
friend.sayName();
6.寄生構(gòu)造函數(shù)模式
創(chuàng)建一個(gè)函數(shù),封裝創(chuàng)建對(duì)象的代碼,再返回新創(chuàng)建的對(duì)象。
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("Icey",25,"Softerware Engineer");
friend.sayName();//"Icey"
通過(guò)在構(gòu)造函數(shù)的末尾添加一個(gè)return語(yǔ)句,可以重寫(xiě)調(diào)用構(gòu)造函數(shù)時(shí)的返回值。
這個(gè)模式可以在特殊情況下用來(lái)為對(duì)象創(chuàng)建構(gòu)造函數(shù)。假設(shè)我們像創(chuàng)建一個(gè)具有額外方法的特殊數(shù)組,由于不能直接修改Array構(gòu)造函數(shù),可以使用這個(gè)模式。
function SpecialArray() {
//創(chuàng)建數(shù)組
var values = new Array();
//添加值
values.push.apply(values,arguments);
//添加方法
values.toPipedString = function () {
return this.join("|");
};
return values;
}
var colors = new SpecialArray("red","blue","green");
alert(colors.toPipedString());//"red|blue|green"
7.穩(wěn)妥構(gòu)造函數(shù)模式
穩(wěn)妥對(duì)象沒(méi)有公共屬性,新創(chuàng)建的實(shí)例方法不引用this,不使用new操作符調(diào)用構(gòu)造函數(shù)。
function Person(name,age,job) {
//創(chuàng)建要返回的對(duì)象
var o = new Object();
//可以在這里定義私有變量和函數(shù)
//添加方法
o.sayName = function () {
alert(name);
};
//返回對(duì)象
return o;
}
在以這種模式創(chuàng)建的對(duì)象中,除了使用sayName()方法之外,沒(méi)有其他辦法訪問(wèn)name的值。
var friend = Person("Icey",25,"Softerware Engineer");
friend.sayName();//"Icey"
它非常適合在某些安全執(zhí)行環(huán)境。