構(gòu)造函數(shù)
在ECMAScript中的構(gòu)造函數(shù)可用來(lái)創(chuàng)建特定類型的對(duì)象。像Object Array這樣的原生的構(gòu)造函數(shù) 在運(yùn)行時(shí)會(huì)自動(dòng)出現(xiàn)在運(yùn)行環(huán)境中。 當(dāng)然 我們也可以創(chuàng)建自定義的構(gòu)造函數(shù), 從而定義自定義對(duì)象類型的屬性和方法。
例如:
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = function() {
alert(this.name)
};
}
var person1 = new Person("張三", 18,"工人");
var person2 = new Person("李四", 25, "包工頭")
在這個(gè)例子中, 我們可以看到與正常函數(shù)的一些不同之處:
1.沒(méi)有顯式地創(chuàng)建對(duì)象;
2.直接將屬性和方法賦給了this對(duì)象;
3. 沒(méi)有return 語(yǔ)句。
此外 我們還注意到函數(shù)名Person的首字母是大寫的,按照慣例 ,構(gòu)造函數(shù)的首字母是大寫的 而非構(gòu)造函數(shù)的首字母是不大寫的。
要?jiǎng)?chuàng)建Person函數(shù)的新實(shí)例, 必須使用new 操作符。 以這種方式調(diào)用構(gòu)造函數(shù)會(huì)經(jīng)歷以下四個(gè)步驟:
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,如下所示
console.log(person1.constructor === Person);//返回true
console.log(person2.constructor === Person);//返回true
對(duì)象的constructor屬性最初是用來(lái)標(biāo)識(shí)對(duì)象類型的。 但是提到檢測(cè)對(duì)象類型,還是使用 instanceof操作符更加可靠一些。我們?cè)谶@個(gè)例子中創(chuàng)建的所有對(duì)象既是Object的實(shí)例 也是Person的實(shí)例,這一點(diǎn)通過(guò)instanceof操作符可以得到驗(yàn)證。
console.log(person1 instanceof Object)//返回true
console.log(person1 instanceof Person)//返回true
console.log(person2 instanceof Object)//返回true
console.log(person1 instanceof Person)//返回true
創(chuàng)建自定義的構(gòu)造函數(shù)意味著將來(lái)可以將它的實(shí)例標(biāo)識(shí)作為一種特定的類型,而這正是構(gòu)造函數(shù)模式勝過(guò)工廠模式的地方。
在這個(gè)例子中, 之所以person1和person2都是Object的實(shí)例,是因?yàn)樗袑?duì)象均繼承自O(shè)bject。
構(gòu)造函數(shù)和其他函數(shù)的唯一區(qū)別, 就是在于調(diào)用他們的方式不同。但是構(gòu)造函數(shù)也是函數(shù), 不存在定義構(gòu)造函數(shù)的特殊語(yǔ)法。 任何函數(shù),只要通過(guò)new操作符來(lái)調(diào)用,那它就可以作為構(gòu)造函數(shù), 而且任何函數(shù),不通過(guò)new 操作符來(lái)調(diào)用, 它就是普通函數(shù)。例如 前面的例子就可以通過(guò)以下任何一種方式來(lái)調(diào)用:
//當(dāng)作普通函數(shù)來(lái)調(diào)用:
Person("柳白猿", 25,"箭士");//添加到了window上面;
window.sayName(); // 返回柳白猿;
//當(dāng)作構(gòu)造函數(shù)來(lái)調(diào)用:
var person = new Person("孫悟空", 500, "行者");
person.sayName();// 孫悟空;
//在另一個(gè)對(duì)象的作用域中調(diào)用
var o = new Object();
Person.call(o,"八戒", 1000, "使者");
o.sayName ()//八戒
構(gòu)造函數(shù)的問(wèn)題
構(gòu)造函數(shù)雖然好用,但也并非沒(méi)有缺點(diǎn)。使用構(gòu)造函數(shù)的主要問(wèn)題,就是每個(gè)方法都要在每個(gè)實(shí)例上重新創(chuàng)建一遍。在前面的例子中 person1和person2都有一個(gè)名為名為sayName的方法,但那個(gè)方法不是同一個(gè)Function的實(shí)例。函數(shù)也是對(duì)象, 因此每定義一個(gè)函數(shù),就是實(shí)例化了一個(gè)對(duì)象。所以不同實(shí)例上的同名函數(shù)是不相等的, 以下代碼可以證明這一點(diǎn)。
console.log(person1.sayName ===person2.sayName)//返回false
然而 創(chuàng)建兩個(gè)完成同樣任務(wù)的Function的實(shí)例的確沒(méi)必要。況且有this對(duì)象在, 根本不用在執(zhí)行代碼前就把函數(shù)綁定到特定對(duì)象的上面。因此,大可以像下面這樣,通過(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;
this.sayName = sayName;
}
function sayName() {
console.log(this.name);
}
var person1 = new Person('心猿意馬', 1500, '不明');
var person2 = new Person('覆水難收', 2000, '所以');
在這個(gè)例子中, 我們把sayName()函數(shù)的定義轉(zhuǎn)移到了構(gòu)造函數(shù)外部。 而在構(gòu)造函數(shù)內(nèi)部,我們把sayName的屬性設(shè)置成等于全局的sayName函數(shù)。這樣一來(lái),由于sayName包含的是一個(gè)指向函數(shù)的指針, 因此person1和person2對(duì)象就共享了在全局作用域中定義的同一個(gè)sayName()函數(shù)。這樣做確實(shí)解決了兩個(gè)函數(shù)做同一件事的問(wèn)題,可是新的問(wèn)題又來(lái)了,在全局作用域中,定義的函數(shù)是實(shí)際上只能被某個(gè)函數(shù)調(diào)用,這讓全局作用域有些名不副實(shí)。而更讓人無(wú)法接受的是,如果對(duì)象需要定義很多的方法,那么就要定義很多個(gè)全局函數(shù),于是我們這個(gè)自定義的引用類型就毫無(wú)意義可言了。 好在, 我們可以通過(guò)使用原型模式很好地解決這個(gè)問(wèn)題。