??在ECMAScript中苫费,引用類型是一種數(shù)據(jù)結(jié)構(gòu)牍汹,用于將數(shù)據(jù)和功能組織在一起钧嘶。它在其他語言中通常被稱作類新荤,但這種稱呼在ECMAScript中并不妥當(dāng),因?yàn)殡m然ECMAScript從技術(shù)上來說是一門面向?qū)ο蟮恼Z言笆呆,但它不具備傳統(tǒng)的面向?qū)ο笳Z言所支持的類和接口等基本結(jié)構(gòu)。引用類型有時(shí)候也被稱作對象定義,因?yàn)樗枋龅氖且活悓ο笏哂械膶傩院头椒ā?/p>
??如前所述味混,對象是某個(gè)特定引用類型的實(shí)例角溃。一個(gè)對象是使用new操作符后接構(gòu)造函數(shù)來創(chuàng)建的茧妒。構(gòu)造函數(shù)本身是一個(gè)函數(shù)萧吠,只不過該函數(shù)是出于創(chuàng)建新對象的目的而定義的。下面是一個(gè)創(chuàng)建對象的例子:
var person = new Object();
上面的代碼創(chuàng)建了Object引用類型的一個(gè)實(shí)例剖毯,然后把該實(shí)例保存在了person變量中逊谋。使用的構(gòu)造函數(shù)是Object()俭令,它只為對象定義了默認(rèn)的屬性和方法。ECMAScript內(nèi)置了許多引用類型(例如Object粟誓,Array等),供開發(fā)人員直接使用蛹头。
1. Object類型
??Object類型是ECMAScript中使用最多的類型顿肺,雖然其不具備太多的功能戏溺,但對于在應(yīng)用程序中存儲和傳輸數(shù)據(jù)來說,確實(shí)是一種非常理想的選擇屠尊。創(chuàng)建Object類型的實(shí)例有兩種方式:
- new操作符后跟Object構(gòu)造函數(shù)
- 使用對象字面量表示法
下面是一個(gè)使用new操作符創(chuàng)建Object實(shí)例的例子:
var person = new Object();
person.name = "Ivan";
person.age = 22;
下面是一個(gè)使用對象字面量表示法創(chuàng)建Object實(shí)例的例子:
var person = {
name: "Ivan",
age: 22
}
我們可以看到旷祸,使用字面量表示法創(chuàng)建對象,簡化了創(chuàng)建包含大量屬性的對象的過程讼昆。另外肋僧,使用對象字面量語法時(shí),如果留空其花括號控淡,則等價(jià)于new Object()
嫌吠,下面是一個(gè)例子:
var person = {};
person.name = "Ivan";
person.age = 22;
在通過字面量定義對象時(shí),實(shí)際上不會調(diào)用Object構(gòu)造函數(shù)掺炭。
??一般來說辫诅,訪問對象屬性時(shí)使用的都是點(diǎn)表示法,這也是很多面向?qū)ο笳Z言通用的語法涧狮。不過炕矮,在JavaScript中,也可以通過方括號([])表示法來訪問對象屬性者冤。在使用方括號表示法時(shí)肤视,應(yīng)該將要訪問的屬性以字符串的形式放在方括號中,如下面的例子所示:
alert(person["name"]); //等價(jià)于alert(person.name)
從功能上看涉枫,這兩種訪問屬性的方式?jīng)]有任何區(qū)別邢滑。但方括號表示法的優(yōu)點(diǎn)在于可以通過變量來訪問屬性,例如:
var propertyName = "name";
alert(person[propertyName]);
另外愿汰,如果屬性名中包含特殊字符困后,也只能使用方括號表示法:
alert(person["first name"]); //屬性名包含空格
通常,除非必須使用變量訪問屬性衬廷,否則我們建議使用點(diǎn)表示法摇予。
2. Function類型
??在ECMAScript中,函數(shù)的本質(zhì)實(shí)際上是對象吗跋。每個(gè)函數(shù)都是Function類型的實(shí)例侧戴,并且與其他引用類型一樣擁有屬性和方法。由于函數(shù)是對象跌宛,因此函數(shù)名實(shí)際上是指向函數(shù)對象的指針酗宋。函數(shù)通常使用函數(shù)聲明語法定義,例如:
function sum(num1, num2){
return num1 + num2;
}
另一種定義函數(shù)的方式是使用函數(shù)表達(dá)式:
var sum = function(num1, num2){
return num1 + num2;
};
由于函數(shù)名僅僅是指向函數(shù)對象的指針秩冈,因此它和其他包含對象指針的變量沒有什么不同本缠。換句話來說斥扛,一個(gè)函數(shù)可能會有多個(gè)名字入问。下面是一個(gè)例子:
function sum(num1, num2){
return num1 + num2;
}
alert(sum(10, 10)); //20
var sum2 = sum;
alert(sum2(10, 10)); //20
sum = null;
alert(sum2(10, 10)); //20
2.1 沒有重載
??將函數(shù)名想象成指針丹锹,也有助于我們理解ECMAScript中為什么沒有函數(shù)重載的概念。下面是一個(gè)例子:
function increase(num){
return num + 1;
}
function increase(num){
return num + 2;
}
alert(increase(100)); //102
上面的例子聲明了兩個(gè)同名函數(shù)芬失,結(jié)果是后面的函數(shù)定義覆蓋了前面一個(gè)楣黍。因?yàn)椋厦娴拇a實(shí)際上與下面的代碼沒有區(qū)別:
var increase = function (num){
return num + 1;
}
increase = function (num){
return num + 2;
}
alert(increase(100)); //102
2.2 函數(shù)聲明與函數(shù)表達(dá)式
??如前文所述棱烂,函數(shù)定義可以通過兩種方式:函數(shù)聲明和函數(shù)表達(dá)式租漂。實(shí)際上,解析器在向執(zhí)行環(huán)境中加載數(shù)據(jù)時(shí)颊糜,對函數(shù)聲明和函數(shù)表達(dá)式并非一視同仁哩治。解析器會率先讀取函數(shù)聲明,并使其在執(zhí)行任何代碼前可用(可以訪問)衬鱼;至于函數(shù)表達(dá)式业筏,則必須等到解析器執(zhí)行到它所在的代碼行,才會被真正解釋執(zhí)行鸟赫。請看下面的例子:
alert(sum(10, 10)); //20
function sum(num1, num2){
return num1 + num2;
}
上面的代碼完全可以正常運(yùn)行蒜胖。因?yàn)樵诖a開始執(zhí)行之前,解析器就通過一個(gè)名為函數(shù)聲明提升(function declaration hoisting)的過程抛蚤,讀取并將函數(shù)聲明添加到執(zhí)行環(huán)境中台谢。然而,將上面的函數(shù)聲明改為等價(jià)的函數(shù)表達(dá)式岁经,就會在執(zhí)行期間產(chǎn)生"unexpected identifier"的錯(cuò)誤朋沮。
除了什么時(shí)候可以通過變量訪問函數(shù)這一點(diǎn)區(qū)別外,函數(shù)聲明與函數(shù)表達(dá)式的語法其實(shí)是等價(jià)的缀壤。
2.3 作為值的函數(shù)
??由于ECMAScript中的函數(shù)名本身就是變量朽们,所以函數(shù)也可以作為值來使用。也就是說诉位,不僅可以像傳遞參數(shù)一樣將一個(gè)函數(shù)傳遞給另一個(gè)函數(shù)骑脱,也可以將一個(gè)函數(shù)作為另一個(gè)函數(shù)的結(jié)果返回。請看下面的例子:
function callSomeFunction(someFunction, someArgument){
return someFunction(someArgument);
}
function plus10(num){
return num + 10;
}
var result1 = callSomeFunction(plus10, 10);
alert(result1); //20
function getGreeeting(name){
return "Hello" + name;
}
var result2 = callSomeFunction(getGreeting, "Ivan");
alert(result2); //"Hello, Ivan"
??另外苍糠,也可以從一個(gè)函數(shù)中返回另一個(gè)函數(shù)叁丧,而且這是一個(gè)極為有用的技術(shù)。下面是一個(gè)根據(jù)傳遞進(jìn)去的對象屬性對一個(gè)對象數(shù)組進(jìn)行排序的例子:
function createComparisionFunction(propertyName){
return funtion(obj1, obj2){
var v1 = obj1[propertyName];
var v2 = obj2[propertyName];
if (v1 < v2){
return -1;
} else if (v1 > v2){
return 1;
}else{
return 0;
}
};
}
var persons = [{name: "Ivan", age: 19}, {name: "Roy", age: 22}];
persons.sort(createComparisionFunction("name"));
alert(persons[0].name); //"Ivan"
persons.sort(createComparisionFunction("age"));
alert(persons[0].age); //19
2.4 函數(shù)內(nèi)部屬性
??在函數(shù)內(nèi)部岳瞭,有兩個(gè)特殊的對象拥娄,分別為arguments和this。argumnets在前文中已經(jīng)提及瞳筏,它是一個(gè)類數(shù)組對象稚瘾,保存中傳入函數(shù)中的所有參數(shù)。而this對象則引用的是函數(shù)據(jù)以執(zhí)行的環(huán)境對象(當(dāng)在網(wǎng)頁的全局作用域中調(diào)用函數(shù)時(shí)姚炕,this對象引用的就是window)摊欠。請看下面的例子:
window.color = "red";
var o = { color: "blue" };
function sayColor(){
alert(this.color);
}
sayColor(); //"red"
o.sayColor = sayColor;
o.sayColor(); //"blue"
上面的代碼在全局作用域中定義了一個(gè)函數(shù)sayColor()丢烘,由于在調(diào)用函數(shù)前,無法確定函數(shù)的執(zhí)行環(huán)境些椒,因此this可能在代碼執(zhí)行過程中引用不同的對象播瞳。當(dāng)在全局作用域中調(diào)用sayColor()函數(shù)時(shí),this引用全局對象window免糕,因此對this.color求值就會轉(zhuǎn)換為對window.color求值赢乓,因此輸出"red"。而當(dāng)把這個(gè)函數(shù)賦值給對象o并調(diào)用o.sayColor()方法時(shí)石窑,this引用的對象是o牌芋,因此this.color求值就會轉(zhuǎn)換為對o.color求值,因此輸出"blue"松逊。
2.5 函數(shù)屬性和方法
??如前文所說姜贡,ECMAScript中的函數(shù)是對象,因此函數(shù)也有屬性和方法棺棵。每個(gè)函數(shù)都包含兩個(gè)屬性:length和prototype楼咳。其中,length表示函數(shù)希望接收的命名參數(shù)個(gè)數(shù)烛恤。而prototype屬性則存在于所有的引用類型中母怜,保存了該引用類型的所有實(shí)例方法。因此缚柏,在自定義引用類型和實(shí)現(xiàn)繼承時(shí)苹熏,prototype屬性是極其重要的。
??另外币喧,每個(gè)函數(shù)都包含了兩個(gè)非繼承而來的方法:apply()和call()轨域。這兩個(gè)方法的用途都是使函數(shù)運(yùn)行在特定的作用域中,實(shí)際上是設(shè)置函數(shù)體內(nèi)this對象的值杀餐。首先apply()方法接收兩個(gè)參數(shù):第一個(gè)參數(shù)是在其中運(yùn)行函數(shù)的作用域干发,第二個(gè)參數(shù)是一個(gè)參數(shù)數(shù)組(或者arguments對象)。下面是一個(gè)例子:
function sum (num1, num2){
return num1 + num2;
}
fucntion callSum1(num1, num2){
return sum.apply(this, arguments);
}
function callSum2(num1, num2){
return sum.apply(this, [num1, num2]);
}
callSum1(10, 10); //20
callSum2(10, 10); //20
在上面這個(gè)例子中史翘,callSum1()在執(zhí)行 sum()函數(shù)時(shí)傳入了 this 作為 this 值(因?yàn)槭窃谌肿饔糜蛑姓{(diào)用的枉长,所以傳入的就是 window 對象)和 arguments 對象。而 callSum2 同樣也調(diào)用了 sum()函數(shù)琼讽,但它傳入的則是 this 和一個(gè)參數(shù)數(shù)組必峰。這兩個(gè)函數(shù)都會正常執(zhí)行并返回正確的結(jié)果。
??call()方法與apply()方法的作用完全相同钻蹬,唯一的區(qū)別在于call()方法接收的是命名參數(shù)列表而不是參數(shù)數(shù)組吼蚁。call()方法和apply()方法真正強(qiáng)大的地方在于它們能夠擴(kuò)充函數(shù)賴以運(yùn)行的作用域。請看下面的例子:
window.color = "red";
var o = { color: "blue" };
function sayColor(){
alert(this.color);
}
sayColor(); //red
sayColor.call(this); //red
sayColor.call(window); //red
sayColor.call(o); //blue
上面的例子在全局環(huán)境中定義了一個(gè)函數(shù):sayColor()问欠。直接在全局環(huán)境中調(diào)用時(shí)肝匆,this指向此函數(shù)運(yùn)行的全局環(huán)境粒蜈,因此對this.color的求值會轉(zhuǎn)換為對window.color的求值。而sayColor.call(this)和sayColor.call(window)則是顯示的設(shè)置此函數(shù)運(yùn)行時(shí)的執(zhí)行環(huán)境术唬,此處設(shè)置為在全局環(huán)境中運(yùn)行sayColor()函數(shù)薪伏,因此也輸出"red"滚澜。而最后使用sayColor.call(o)調(diào)用則將執(zhí)行環(huán)境切換到對象o粗仓,此時(shí)函數(shù)體內(nèi)的 this 對象指向了 o,因此結(jié)果顯示的是"blue"设捐。
??使用 call()(或 apply())來擴(kuò)充作用域的大好處借浊,就是對象不需要與方法有任何耦合關(guān)系。在前面例子的第一個(gè)版本中萝招,我們是先將 sayColor()函數(shù)放到了對象 o 中蚂斤,然后再通過 o 來調(diào)用它的;而在這里重寫的例子中槐沼,就不需要先前那個(gè)多余的步驟了曙蒸。
??ECMAScript 5還定義了一個(gè)方法:bind()。這個(gè)方法會創(chuàng)建一個(gè)函數(shù)的實(shí)例岗钩,其 this 值會被綁 定到傳給 bind()函數(shù)的值纽窟。例如:
window.color = "red";
var o = { color: "blue" };
function sayColor(){
alert(this.color);
}
var objectSayColor = sayColor.bind(o);
objectSayColor(); //blue
在這個(gè)例子中,sayColor()調(diào)用 bind()并傳入對象 o兼吓,創(chuàng)建了 objectSayColor()函數(shù)臂港。object- SayColor()函數(shù)的 this 值等于 o,因此即使是在全局作用域中調(diào)用這個(gè)函數(shù)视搏,也會看到"blue"审孽。