構(gòu)造函數(shù)
在ECMAScript中的構(gòu)造函數(shù)可用來創(chuàng)建特定類型的對象。像Object Array這樣的原生的構(gòu)造函數(shù) 在運行時會自動出現(xiàn)在運行環(huán)境中。 當然 我們也可以創(chuàng)建自定義的構(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("張三"瞬逊, 18檐迟,"工人");
var person2 = new Person("李四"码耐, 25追迟, "包工頭")
在這個例子中, 我們可以看到與正常函數(shù)的一些不同之處:
1.沒有顯式地創(chuàng)建對象骚腥;
2.直接將屬性和方法賦給了this對象敦间;
3. 沒有return 語句。
此外 我們還注意到函數(shù)名Person的首字母是大寫的束铭,按照慣例 廓块,構(gòu)造函數(shù)的首字母是大寫的 而非構(gòu)造函數(shù)的首字母是不大寫的。
要創(chuàng)建Person函數(shù)的新實例契沫, 必須使用new 操作符带猴。 以這種方式調(diào)用構(gòu)造函數(shù)會經(jīng)歷以下四個步驟:
1.創(chuàng)建一個新對象;
2.將構(gòu)造函數(shù)的作用域賦給新對象(因此this就指向了這個新對象懈万;
3. 執(zhí)行構(gòu)造函數(shù)中的代碼(為這個新對象添加新屬性)拴清;
4.返回新對象。
在前面例子的最后会通,person1和person2分別保存著Person的一個不同的實例口予。 這兩個對象都有一個constructor(構(gòu)造函數(shù)屬性),都指向了Person涕侈,如下所示
console.log(person1.constructor === Person);//返回true
console.log(person2.constructor === Person);//返回true
對象的constructor屬性最初是用來標識對象類型的沪停。 但是提到檢測對象類型,還是使用 instanceof操作符更加可靠一些裳涛。我們在這個例子中創(chuàng)建的所有對象既是Object的實例 也是Person的實例木张,這一點通過instanceof操作符可以得到驗證。
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ù)意味著將來可以將它的實例標識作為一種特定的類型端三,而這正是構(gòu)造函數(shù)模式勝過工廠模式的地方舷礼。
在這個例子中, 之所以person1和person2都是Object的實例技肩,是因為所有對象均繼承自O(shè)bject且轨。
構(gòu)造函數(shù)和其他函數(shù)的唯一區(qū)別浮声, 就是在于調(diào)用他們的方式不同虚婿。但是構(gòu)造函數(shù)也是函數(shù), 不存在定義構(gòu)造函數(shù)的特殊語法泳挥。 任何函數(shù)然痊,只要通過new操作符來調(diào)用,那它就可以作為構(gòu)造函數(shù)屉符, 而且任何函數(shù)剧浸,不通過new 操作符來調(diào)用锹引, 它就是普通函數(shù)。例如 前面的例子就可以通過以下任何一種方式來調(diào)用:
//當作普通函數(shù)來調(diào)用:
Person("柳白猿"唆香, 25嫌变,"箭士");//添加到了window上面躬它;
window.sayName(); // 返回柳白猿腾啥;
//當作構(gòu)造函數(shù)來調(diào)用:
var person = new Person("孫悟空", 500冯吓, "行者")倘待;
person.sayName();// 孫悟空;
//在另一個對象的作用域中調(diào)用
var o = new Object();
Person.call(o,"八戒"组贺, 1000凸舵, "使者");
o.sayName ()//八戒
構(gòu)造函數(shù)的問題
構(gòu)造函數(shù)雖然好用失尖,但也并非沒有缺點啊奄。使用構(gòu)造函數(shù)的主要問題,就是每個方法都要在每個實例上重新創(chuàng)建一遍掀潮。在前面的例子中 person1和person2都有一個名為名為sayName的方法增热,但那個方法不是同一個Function的實例。函數(shù)也是對象胧辽, 因此每定義一個函數(shù)峻仇,就是實例化了一個對象。所以不同實例上的同名函數(shù)是不相等的邑商, 以下代碼可以證明這一點摄咆。
console.log(person1.sayName ===person2.sayName)//返回false
然而 創(chuàng)建兩個完成同樣任務的Function的實例的確沒必要。況且有this對象在人断, 根本不用在執(zhí)行代碼前就把函數(shù)綁定到特定對象的上面吭从。因此,大可以像下面這樣恶迈,通過函數(shù)定義轉(zhuǎn)移到構(gòu)造函數(shù)外邊來解決這個問題涩金。
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奈附, '所以')全度;
在這個例子中, 我們把sayName()函數(shù)的定義轉(zhuǎn)移到了構(gòu)造函數(shù)外部斥滤。 而在構(gòu)造函數(shù)內(nèi)部将鸵,我們把sayName的屬性設(shè)置成等于全局的sayName函數(shù)勉盅。這樣一來,由于sayName包含的是一個指向函數(shù)的指針顶掉, 因此person1和person2對象就共享了在全局作用域中定義的同一個sayName()函數(shù)草娜。這樣做確實解決了兩個函數(shù)做同一件事的問題,可是新的問題又來了痒筒,在全局作用域中驱还,定義的函數(shù)是實際上只能被某個函數(shù)調(diào)用,這讓全局作用域有些名不副實凸克。而更讓人無法接受的是议蟆,如果對象需要定義很多的方法,那么就要定義很多個全局函數(shù)萎战,于是我們這個自定義的引用類型就毫無意義可言了咐容。 好在, 我們可以通過使用原型模式很好地解決這個問題蚂维。