JavaScript 是面向?qū)ο蟮哪_本語言,長期以來用作 Web 瀏覽器應(yīng)用程序的客戶端腳本接口。JavaScript 讓 Web 開發(fā)人員能以編程方式處理 Web 頁面上的對象,并提供了一個(gè)能夠動(dòng)態(tài)操作這些對象的平臺(tái)。在最初引入 JavaScript 時(shí),通常只用于提供 Web 頁面上的一些不重要的特性,如時(shí)鐘功能和瀏覽器狀態(tài)欄中的滾動(dòng)文本等。
另一個(gè)常見的特性是 “rolloverlink”,就是當(dāng)用戶將鼠標(biāo)移到圖片或文本鏈接上時(shí),圖片或文本鏈接的顏色會(huì)改變。然而,近年來,隨著AsynchronousJavaScript and XML (Ajax) 概念將基于 Web 的編程的交互性提升到一個(gè)新高度,JavaScript 也變得越來越重要。在出現(xiàn) Ajax 之前,所有服務(wù)器端處理或數(shù)據(jù)庫訪問都需要 “刷新” 整個(gè)頁面或通過瀏覽器呈現(xiàn)一個(gè)新頁面。這不僅減慢了速度并使用戶感到沮喪,而且還浪費(fèi)帶寬和資源。
JavaScript原型
原型是JavaScript中一個(gè)比較難理解的概念,原型相關(guān)的屬性也比較多,對象有“[[prototype]]”屬性,函數(shù)對象有“prototype”屬性,原型對象有“constructor”屬性。
為了弄清原型,以及原型相關(guān)的這些屬性關(guān)系,就有了這篇文章。
相信通過這篇文章一定能夠清楚的認(rèn)識(shí)到原型,現(xiàn)在就開始原型之旅吧。
認(rèn)識(shí)原型
開始原型的介紹之前,首先來認(rèn)識(shí)一下什么是原型?
在JavaScript中,原型也是一個(gè)對象,通過原型可以實(shí)現(xiàn)對象的屬性繼承,JavaScript的對象中都包含了一個(gè)“ [[Prototype]]”內(nèi)部屬性,這個(gè)屬性所對應(yīng)的就是該對象的原型。
“[[Prototype]]”作為對象的內(nèi)部屬性,是不能被直接訪問的。所以為了方便查看一個(gè)對象的原型,F(xiàn)irefox和Chrome中提供了“__proto__”這個(gè)非標(biāo)準(zhǔn)(不是所有瀏覽器都支持)的訪問器(ECMA引入了標(biāo)準(zhǔn)對象原型訪問器“Object.getPrototype(object)”)。
實(shí)例分析
下面通過一個(gè)例子來看看原型相關(guān)概念:
this.name = name;
this.age = age;
this.getInfo = function(){
console.log(this.name + “ is ” + this.age + “ years old”);
};
}
var will = new Person(“Will”, 28);
在上面的代碼中,通過了Person這個(gè)構(gòu)造函數(shù)創(chuàng)建了一個(gè)will對象。下面就通過will這個(gè)對象一步步展開了解原型。
Step 1: 查看對象will的原型
通過下面代碼,可以查看對象will的原型:
console.log(will.__proto__);
console.log(will.constructor);
結(jié)果分析:
“Person {}”對象就是對象will的原型,通過Chrome展開可以看到,“Person {}”作為一個(gè)原型對象,也有“__proto__”屬性(對應(yīng)原型的原型)。
在這段代碼中,還用到了“constructor”屬性。在JavaScript的原型對象中,還包含一個(gè)“constructor”屬性,這個(gè)屬性對應(yīng)創(chuàng)建所有指向該原型的實(shí)例的構(gòu)造函數(shù)。
通過“constructor”這個(gè)屬性,我們可以來判斷一個(gè)對象是不是數(shù)組類型
function isArray(myArray) {
return myArray.constructor.toString().indexOf(“Array”) 》 -1;
}
在這里,will對象本身并沒有“constructor”這個(gè)屬性,但是通過原型鏈查找,找到了will原型(will.__proto__)的“constructor”屬性,并得到了Person函數(shù)。
Step 2: 查看對象will的原型(will.__proto__)的原型
既然will的原型“Person {}”也是一個(gè)對象,那么我們就同樣可以來查看“will的原型(will.__proto__)的原型”。
運(yùn)行下面的代碼:
console.log(will.__proto__ === Person.prototype);
console.log(Person.prototype.__proto__);
console.log(Person.prototype.constructor);
console.log(Person.prototype.constructor === Person);
結(jié)果分析:
首先看 “will.__proto__ === Person.prototype”,在JavaScript中,每個(gè)函數(shù)都有一個(gè)prototype屬性,當(dāng)一個(gè)函數(shù)被用作構(gòu)造函數(shù)來創(chuàng)建實(shí)例時(shí),該函數(shù)的prototype屬性值將被作為原型賦值給所有對象實(shí)例(也就是設(shè)置實(shí)例的__proto__屬性),也就是說,所有實(shí)例的原型引用的是函數(shù)的prototype屬性。了解了構(gòu)造函數(shù)的prototype屬性之后,一定就明白為什么第一句結(jié)果為true了。
prototype屬性是函數(shù)對象特有的,如果不是函數(shù)對象,將不會(huì)有這樣一個(gè)屬性。
當(dāng)通過“Person.prototype.__proto__”語句獲取will對象原型的原型時(shí)候,將得到“Object {}”對象,后面將會(huì)看到所有對象的原型都將追溯到“Object {}”對象。
對于原型對象“Person.prototype”的“constructor”,根據(jù)前面的介紹,將對應(yīng)Person函數(shù)本身。
通過上面可以看到,“Person.prototype”對象和Person函數(shù)對象通過“constructor”和“prototype”屬性實(shí)現(xiàn)了相互引用(后面會(huì)有圖展示這個(gè)相互引用的關(guān)系)。
Step 3: 查看對象Object的原型
通過前一部分可以看到,will的原型的原型是“Object {}”對象。實(shí)際上在JavaScript中,所有對象的原型都將追溯到“Object {}”對象。
下面通過一段代碼看看“Object {}”對象:
console.log(Person.prototype.__proto__ === Object.prototype);
console.log(typeof Object);
console.log(Object);
console.log(Object.prototype);
console.log(Object.prototype.__proto__);
console.log(Object.prototype.constructor);
通過下面的代碼可以看到:
Object對象本身是一個(gè)函數(shù)對象。
既然是Object函數(shù),就肯定會(huì)有prototype屬性,所以可以看到“Object.prototype”的值就是“Object {}”這個(gè)原型對象。
反過來,當(dāng)訪問“Object.prototype”對象的“constructor”這個(gè)屬性的時(shí)候,就得到了Obejct函數(shù)。
另外,當(dāng)通過“Object.prototype.__proto__”獲取Object原型的原型的時(shí)候,將會(huì)得到“null”,也就是說“Object {}”原型對象就是原型鏈的終點(diǎn)了。
Step 4: 查看對象Function的原型
在上面的例子中,Person是一個(gè)構(gòu)造函數(shù),在JavaScript中函數(shù)也是對象,所以,我們也可以通過“__proto__”屬性來查找Person函數(shù)對象的原型。
console.log(Person.__proto__ === Function.prototype);
console.log(Person.constructor === Function)
console.log(typeof Function);
console.log(Function);
console.log(Function.prototype);
console.log(Function.prototype.__proto__);
console.log(Function.prototype.constructor);
結(jié)果分析 :
在JavaScript中有個(gè)Function對象(類似Object),這個(gè)對象本身是個(gè)函數(shù);所有的函數(shù)(包括Function,Object)的原型(__proto__)都是“Function.prototype”。
Function對象作為一個(gè)函數(shù),就會(huì)有prototype屬性,該屬性將對應(yīng)“function () {}”對象。
Function對象作為一個(gè)對象,就有“__proto__”屬性,該屬性對應(yīng)“Function.prototype”,也就是說,“Function.__proto__ === Function.prototype”
對于Function的原型對象“Function.prototype”,該原型對象的“__proto__”屬性將對應(yīng)“Object {}”
對比prototype和__proto__
對于“prototype”和“__proto__”這兩個(gè)屬性有的時(shí)候可能會(huì)弄混,“Person.prototype”和“Person.__proto__”是完全不同的。
在這里對“prototype”和“__proto__”進(jìn)行簡單的介紹:
對于所有的對象,都有__proto__屬性,這個(gè)屬性對應(yīng)該對象的原型
對于函數(shù)對象,除了__proto__屬性之外,還有prototype屬性,當(dāng)一個(gè)函數(shù)被用作構(gòu)造函數(shù)來創(chuàng)建實(shí)例時(shí),該函數(shù)的prototype屬性值將被作為原型賦值給所有對象實(shí)例(也就是設(shè)置實(shí)例的__proto__屬性)
圖解實(shí)例
通過上面結(jié)合實(shí)例的分析,相信你一定了解了原型中的很多內(nèi)容。
但是現(xiàn)在肯定對上面例子中的關(guān)系感覺很凌亂,一會(huì)兒原型,一會(huì)兒原型的原型,還有Function,Object,constructor,prototype等等關(guān)系。
現(xiàn)在就對上面的例子中分析得到的結(jié)果/關(guān)系進(jìn)行圖解,相信這張圖可以讓你豁然開朗。
對于上圖的總結(jié)如下:
所有的對象都有“__proto__”屬性,該屬性對應(yīng)該對象的原型
所有的函數(shù)對象都有“prototype”屬性,該屬性的值會(huì)被賦值給該函數(shù)創(chuàng)建的對象的“__proto__”屬性
所有的原型對象都有“constructor”屬性,該屬性對應(yīng)創(chuàng)建所有指向該原型的實(shí)例的構(gòu)造函數(shù)
函數(shù)對象和原型對象通過“prototype”和“constructor”屬性進(jìn)行相互關(guān)聯(lián)
通過原型改進(jìn)例子
在上面例子中,“getInfo”方法是構(gòu)造函數(shù)Person的一個(gè)成員,當(dāng)通過Person構(gòu)造兩個(gè)實(shí)例的時(shí)候,每個(gè)實(shí)例都會(huì)包含一個(gè)“getInfo”方法。
var will = new Person(“Will”, 28);
var wilber = new Person(“Wilber”, 27);
前面了解到,原型就是為了方便實(shí)現(xiàn)屬性的繼承,所以可以將“getInfo”方法當(dāng)作Person原型(Person.__proto__)的一個(gè)屬性,這樣所有的實(shí)例都可以通過原型繼承的方式來使用“getInfo”這個(gè)方法了。
所以對例子進(jìn)行如下修改:
function Person(name, age){
this.name = name;
this.age = age;
}
Person.prototype.getInfo = function(){
console.log(this.name + “ is ” + this.age + “ years old”);
};
修改后的結(jié)果為:
原型鏈
因?yàn)槊總€(gè)對象和原型都有原型,對象的原型指向?qū)ο蟮母?,而父的原型又指向父的父,這種原型層層連接起來的就構(gòu)成了原型鏈。
在“理解JavaScript的作用域鏈”一文中,已經(jīng)介紹了標(biāo)識(shí)符和屬性通過作用域鏈和原型鏈的查找。
這里就繼續(xù)看一下基于原型鏈的屬性查找。
屬性查找
當(dāng)查找一個(gè)對象的屬性時(shí),JavaScript 會(huì)向上遍歷原型鏈,直到找到給定名稱的屬性為止,到查找到達(dá)原型鏈的頂部(也就是 “Object.prototype”), 如果仍然沒有找到指定的屬性,就會(huì)返回 undefined。
看一個(gè)例子:
function Person(name, age){
this.name = name;
this.age = age;
}
Person.prototype.MaxNumber = 9999;
Person.__proto__.MinNumber = -9999;
var will = new Person(“Will”, 28);
console.log(will.MaxNumber);
// 9999
console.log(will.MinNumber);
// undefined
在這個(gè)例子中分別給“Person.prototype ”和“ Person.__proto__”這兩個(gè)原型對象添加了“MaxNumber ”和“MinNumber”屬性,這里就需要弄清“prototype”和“__proto__”的區(qū)別了。
“Person.prototype ”對應(yīng)的就是Person構(gòu)造出來所有實(shí)例的原型,也就是說“Person.prototype ”屬于這些實(shí)例原型鏈的一部分,所以當(dāng)這些實(shí)例進(jìn)行屬性查找時(shí)候,就會(huì)引用到“Person.prototype ”中的屬性。
屬性隱藏
當(dāng)通過原型鏈查找一個(gè)屬性的時(shí)候,首先查找的是對象本身的屬性,如果找不到才會(huì)繼續(xù)按照原型鏈進(jìn)行查找。
這樣一來,如果想要覆蓋原型鏈上的一些屬性,我們就可以直接在對象中引入這些屬性,達(dá)到屬性隱藏的效果。
看一個(gè)簡單的例子:
function Person(name, age){
this.name = name;
this.age = age;
}
Person.prototype.getInfo = function(){
console.log(this.name + “ is ” + this.age + “ years old”);
};
var will = new Person(“Will”, 28);
will.getInfo = function(){
console.log(“getInfo method from will instead of prototype”);
};
will.getInfo();
// getInfo method from will instead of prototype
對象創(chuàng)建方式影響原型鏈
會(huì)到本文開始的例子,will對象通過Person構(gòu)造函數(shù)創(chuàng)建,所以will的原型(will.__proto__)就是“Person.prototype”。
同樣,我們可以通過下面的方式創(chuàng)建一個(gè)對象:
var July = {
name: “July”,
age: 28,
getInfo: function(){
console.log(this.name + “ is ” + this.age + “ years old”);
},
}
console.log(July.getInfo());
當(dāng)使用這種方式創(chuàng)建一個(gè)對象的時(shí)候,原型鏈就變成下圖了,July對象的原型是“Object.prototype”也就是說對象的構(gòu)建方式會(huì)影響原型鏈的形式。
hasOwnProperty
“hasOwnProperty”是“Object.prototype”的一個(gè)方法,該方法能判斷一個(gè)對象是否包含自定義屬性而不是原型鏈上的屬性,因?yàn)椤癶asOwnProperty” 是 JavaScript 中唯一一個(gè)處理屬性但是不查找原型鏈的函數(shù)。
相信你還記得文章最開始的例子中,通過will我們可以訪問“constructor”這個(gè)屬性,并得到will的構(gòu)造函數(shù)Person。這里結(jié)合“hasOwnProperty”這個(gè)函數(shù)就可以看到,will對象并沒有“constructor”這個(gè)屬性。
從下面的輸出可以看到,“constructor”是will的原型(will.__proto__)的屬性,但是通過原型鏈的查找,will對象可以發(fā)現(xiàn)并使用“constructor”屬性。
“hasOwnProperty”還有一個(gè)重要的使用場景,就是用來遍歷對象的屬性。
function Person(name, age){
this.name = name;
this.age = age;
}
Person.prototype.getInfo = function(){
console.log(this.name + “ is ” + this.age + “ years old”);
};
var will = new Person(“Will”, 28);
for(var attr in will){
console.log(attr);
}
// name
// age
// getInfo
for(var attr in will){
if(will.hasOwnProperty(attr)){
console.log(attr);
}
}
// name
// age
什么是JavaScript 框架?
JavaScript 本身就是一種功能強(qiáng)大的語言,您不需要額外的框架就可創(chuàng)建富互聯(lián)網(wǎng)應(yīng)用程序(RIA)。然而使用JavaScript 并不是件容易的事,主要是由于支持多個(gè) Web 瀏覽器產(chǎn)生的復(fù)雜性。與 HTML 和 CSS一樣,不同的瀏覽器有不同的 JavaScript 實(shí)現(xiàn)。讓 JavaScript 代碼實(shí)現(xiàn)跨瀏覽器兼容簡直是個(gè)噩夢。
JavaScript 框架或庫是一組能輕松生成跨瀏覽器兼容的 JavaScript 代碼的工具和函數(shù)。每一個(gè)庫都在眾多流行的 Web瀏覽器的現(xiàn)代版本上進(jìn)行了可靠的測試,因此,您可以放心地使用這些框架,您的基于 JavaScript 的 RIA 將會(huì)在不同瀏覽器和平臺(tái)上以類似的方式工作。
除了解決跨瀏覽器問題,使用 JavaScript 框架可以更容易地編寫檢索、遍歷、操作 DOM 元素的代碼。它們不僅提供獲取 DOM 元素引用的快捷函數(shù),而且還允許 DOM 遍歷函數(shù)以菊花鏈(daisy-chaining)方式查找任意深度的父元素、子元素、兄弟元素。最后,框架還提供一系列函數(shù)來更輕松地操作這些對象,可以改變、添加或刪除內(nèi)容本身;或者使用 CSS 樣式類來改變元素的外觀。
框架的另一重要特性是其改進(jìn)的事件處理支持。由于不同瀏覽器的實(shí)現(xiàn)方式各不相同,跨瀏覽器事件處理將會(huì)非常艱難。因此 JavaScript 框架通常封裝瀏覽器事件,并提供一組有用的跨瀏覽器兼容的函數(shù)來進(jìn)行處理。有些框架還會(huì)提供一組標(biāo)準(zhǔn)鍵盤代碼來表示基于鍵盤的事件(如按下 Escape 鍵、Return 鍵、光標(biāo)鍵,等等)。
所有這些特性都非常有用,但 JavaScript 框架有一個(gè)特性對于它最近的流行非常重要 — 支持 Ajax。與 JavaScript 的其他許多方面一樣,每個(gè) Web 瀏覽器往往以不同方式支持 Ajax,這使得以一種在所有 Web 瀏覽器中都受支持的方式處理 Ajax 變得十分復(fù)雜。幾乎所有 JavaScript 框架都包含某種形式的 Ajax 庫支持,通常提供 Ajax 請求和響應(yīng)對象,以及用于評價(jià)響應(yīng)、更新 DOM 元素、查詢特定請求的幫助函數(shù)(helper)。
JavaScript 框架的典型特性
現(xiàn)在,讓我們看一看大多數(shù) JavaScript 框架都具備的有用特性。包括:
· 選擇器(Selector)
· DOM 遍歷
· DOM 操作
· 實(shí)用(Utility)函數(shù)
· 事件處理
· Ajax
在解釋每個(gè)特性時(shí),我將會(huì)用以下的一個(gè)或幾個(gè) JavaScript 框架舉例說明:Prototype、jQuery、YUI、ExtJS 和 MooTools。盡管每個(gè)框架的實(shí)現(xiàn)和語法都各不相同,但概念都是相同的。每個(gè)框架都有一個(gè)詳細(xì)的 API 參考,可幫助您理解如何使用該特定庫中的特性。
?
選擇器
大多數(shù)可用的 JavaScript 框架都會(huì)實(shí)現(xiàn)某種形式的對快速元素選取的支持。通常來說,這些選擇器會(huì)使獲得 HTML 元素引用的過程快很多,并允許通過 ID、類名、元素類型甚至使用一組偽選擇器(pseudo-selector)來查找元素。
例如,使用常規(guī) JavaScript,您也許會(huì)用以下代碼通過 ID 來選擇 DOM 元素:
var theElement = document.getElementById(‘the_element’);
與其他框架一樣,MooTools 提供了執(zhí)行此操作的快捷方法。除了選取該元素,MooTools 還可通過一系列實(shí)用函數(shù)擴(kuò)展此元素。其語法如下:
var theElement = $(‘the_element’);
如上所示的單美元符號(hào)(dollar)函數(shù),在很多(但不是所有)流行的 JavaScript 框架中都可用,而且語法也大體一致。Prototype 庫則更進(jìn)一步,允許通過 ID 一次選取多個(gè)元素,并返回元素?cái)?shù)組。和 MooTools 一樣,可用Prototype 實(shí)用函數(shù)擴(kuò)展這些元素。用 Prototype 一次選取多個(gè)元素的語法是:
var elementArray = $(‘element_one’, ‘element_two’,‘element_three’);
在 實(shí)用函數(shù) 一節(jié)中,您將會(huì)學(xué)到更多 JavaScript 框架所提供的簡化集合迭代的函數(shù)。
在前面的例子中,必須提供需要選取的元素的 ID。然而,如果要選取多個(gè)元素(例如,所有圖片)或是具有特定 CSS類名的所有表行,那又怎么辦呢?MooTools(還有其他庫)提供了一個(gè)簡單的方法來實(shí)現(xiàn)此功能 — 雙美元符號(hào)(dollar-dollar)函數(shù)。它的工作方式與單美元符號(hào)函數(shù)相同,不同之處在于它接受 CSS 元素名、類名、偽選擇器作為參數(shù),而不是接受元素 ID 作為參數(shù)。例如,要使用 MooTools 選取 Web 頁面上的所有圖片,將用以下代碼:
var allImages = $$(‘img’);
這將返回一個(gè)包含文檔中的所有圖片的數(shù)組,其中每一個(gè)圖片都使用單美元符號(hào)函數(shù)進(jìn)行擴(kuò)展,以包含 MooTools 實(shí)用函數(shù)。
根據(jù)標(biāo)記名選取元素非常有用,但如果只是想根據(jù) CSS 類選擇一個(gè)元素子集,該怎么辦呢?這也很簡單。在下面的例子中,MooTools 將會(huì)選擇 CSS 類名為 “odd” 的所有表行。這在實(shí)現(xiàn)表行條狀化(在表行之間交替變化背景色)時(shí)將非常有用:
var allOddRows = $$(‘tr.odd’);
實(shí)際上,MooTools 提供了實(shí)現(xiàn)表行條狀化(row striping)的更好方法。在上面的例子中,假設(shè)表中的所有奇數(shù)行的CSS 類名為 “odd”。以下代碼不要求對表行定義任何 CSS 類名:
var allOddRows = $$(‘tbody:odd’);
這是一個(gè)偽選擇器的例子,將會(huì)返回所有符合條件的對象,在本例中為頁面中的 tbody(表主體)的所有奇數(shù)子元素。MooTools 偽選擇器的其他例子包括:
· checked— 選取所有選中的元素(例如,選中的復(fù)選框)
· enabled— 選取所有啟用的元素
· contains— 選取所有包含作為參數(shù)傳遞給選擇器的文本的元素(例如,contains(‘thistext’))
如前所述,并非所有 JavaScript 框架都使用單美元符號(hào)函數(shù)選取 DOM 元素。在 YUI (Yahoo!User Interface) 庫第 3 版中,用以下代碼根據(jù) ID 選取元素(請注意 YUI 3 要求在 ID 前傳遞 ID 選擇器符號(hào) #):
var theElement = Y.one(‘#the_element’);
同樣,與使用雙美元符號(hào)函數(shù)根據(jù)標(biāo)記或類名檢索元素不同的是,YUI 使用了 Y.all 函數(shù):
var allOddRows = Y.all(‘tr.odd’);
ExtJS 使用類似的方式,用以下語法根據(jù) ID 選取元素:
var theElement = Ext.get(‘the_element’);
以下語法用于根據(jù)標(biāo)記和類名選取元素:
var allOddRows = Ext.select(‘tr.odd’);
在下一節(jié)中,您將看到 JavaScript 框架如何輕松遍歷 DOM,換句話說,就是查找選定元素的父元素、子元素、兄弟元素。您還會(huì)學(xué)到如何使用庫操作 DOM 以修改元素。
?
DOM 遍歷
根據(jù) ID、元素類型或 CSS 類名查找元素非常有用,但如何根據(jù)元素在 DOM 樹中的位置執(zhí)行查找呢?換而言之,根據(jù)一個(gè)給定的元素查找其父元素、子元素、前一個(gè)或后一個(gè)兄弟元素。例如,看一下清單 1 的 HTML 片段。
清單 1. HTML 片段(一個(gè) HTML 表)
《table》 《thead》 《tr》《th》Name《/th》 《th》Email Address《/th》《th》Actions《/th》 《/tr》 《/thead》 《tbody》 《trid=“row-001”》 《td》Joe Lennon《/td》《td》[email protected]《/td》 《td》《ahref=“#”》Edit《/a》? 《ahref=“#”》Delete《/a》《/td》 《/tr》 《trid=“row-002”》 《td》Jill Mac Sweeney《/td》 《td》[email protected]《/td》《td》《a href=“#”》Edit《/a》? 《ahref=“#”》Delete《/a》《/td》 《/tr》《/tbody》《/table》
清單 1 用縮進(jìn)表示每個(gè)元素在 DOM 節(jié)點(diǎn)樹中的位置。在該例中,table 元素是根元素,它有兩個(gè)子節(jié)點(diǎn),thead 和 tbody。thead 元素只有一個(gè)子節(jié)點(diǎn) tr,后者有三個(gè)子節(jié)點(diǎn) — 所有 th 元素。tbody 元素有兩個(gè)子節(jié)點(diǎn),均為 tr 元素,每個(gè) tr 元素又有三個(gè)子元素。每行的第三個(gè)子元素又有兩個(gè)子節(jié)點(diǎn),都是 a (錨點(diǎn))標(biāo)記。
如您所知,可以使用 JavaScript 框架的 Selector 函數(shù)根據(jù) ID 輕松選取元素。在該例中,有兩個(gè)元素具有 ID — 均為 tr(表行)元素,ID 分別為 row-001 和 row-002。要使用 Prototype 選取第一個(gè) tr 元素,需要用到以下代碼:
var theRow = $(‘row-001’);
在前面的小節(jié)中,您學(xué)會(huì)了如何使用選擇器根據(jù)類型或 CSS 類檢索元素。在本例中,可以使用以下語法選取所有 td 元素。
var allCells = $$(‘td’);
這段代碼的問題是它將返回 DOM 中的所有 td 元素。但是,如果只希望獲取 ID 為 row-001 的行中的 td 元素,怎么辦呢?這時(shí)就該使用 DOM 遍歷函數(shù)了。首先,使用 Prototype 選取 ID 為 row-001 的 tr 元素的所有子節(jié)點(diǎn):
var firstRowCells = theRow.childElements();
這將返回 theRow 變量(之前已設(shè)為 ID 為 row-001 的元素)的所有子元素的數(shù)組。
下一步,假設(shè)只希望取得該行的第一個(gè)子節(jié)點(diǎn),在本例中,是內(nèi)容為 “Joe Lennon” 的 td 元素。應(yīng)使用以下語句:
var firstRowFirstCell = theRow.down();
很簡單吧?這種特別的用法等價(jià)于:
var firstRowFirstCell = theRow.childElements()[0];
也可以表示為:
var firstRowFirstCell = theRow.down(0);
JavaScript 索引值從零(0)開始,所以以上語句實(shí)際上是告訴 JavaScript 選取第一個(gè)子元素。要選取第二個(gè)子元素(包含[email protected] 郵件地址的單元格),可以使用下面的語句:
var firstRowSecondCell = theRow.down(1);
或者,可以在 DOM 兄弟節(jié)點(diǎn)間導(dǎo)航。本例中,第二個(gè)單元格是第一個(gè)單元格的下一個(gè)兄弟節(jié)點(diǎn),因此可以使用以下語句:
var firstRowSecondCell = firstRowFirstCell.next();
這與 down() 函數(shù)使用了相同的方式,因此可以使用下面的語句選擇第三個(gè)單元格:
var firstRowThirdCell = firstRowFirstCell.next(1);
除了使用索引查找特定節(jié)點(diǎn)外,Prototype 還允許使用 CSS 選擇器語法。考慮 清單 1 的例子,找到包含 Jill Mac Sweeney 的明細(xì)的行的第二個(gè)鏈接(“Delete” 鏈接):
var secondRowSecondLink = $(‘row-002’).down(‘a(chǎn)’, 1);
在本例中,可以使用美元符號(hào)函數(shù)找到 ID 為 row-002 的元素,然后向下遍歷 DOM,直到找到下一個(gè)后代 a(錨點(diǎn))元素。
有些框架可以使用 “菊花鏈” 遍歷函數(shù),表示可以將遍歷命令互相連接。在 Prototype 中實(shí)現(xiàn)前一個(gè)例子的另一種方法是:
var secondRowSecondLink = $(‘row-002’).down(‘a(chǎn)’).next();
考慮下面的例子:
var domTraversal =$(‘row-001’).down().up().next().previous();
如您所見,菊花鏈方式可以將幾個(gè) DOM 遍歷語句連接起來。實(shí)際上,上例實(shí)際上選擇 tr 元素 row-001,因此菊花鏈剛好回到了起點(diǎn)!
DOM 操作
上文中,您已經(jīng)看到如何使用 JavaScript 框架的選擇器和 DOM 遍歷來簡化特定元素的選取。然而,要想改變 Web 頁面中的特定元素的外觀或內(nèi)容,需要操作 DOM 并應(yīng)用改變。如果使用純 JavaScript 將會(huì)非常繁瑣,幸運(yùn)的是,大多數(shù)JavaScript 框架提供了有用的函數(shù),簡化了這些操作。
假設(shè)您有一個(gè) div 元素,其 id 是 the-box:
《div id=“the-box”》Message goeshere《/div》
如果要用 jQuery 改變 “Message goes here” 文本,方法如下:
$(‘the-box’).html(‘This is the new message!’);
實(shí)際上,可以在函數(shù)內(nèi)部使用 HTML 代碼,它將由瀏覽器解析。例如:
$(‘the-box’).html(‘This is the 《strong》new《/strong》message!’);
在本例中,div 元素的內(nèi)容在 DOM 中呈現(xiàn)為:
《div id=“the-box”》This is the《strong》new《/strong》 message!《/div》
當(dāng)然,在一些情況下您需要使用特殊字符,如大于號(hào)或小于號(hào)??梢圆恢付▽iT的 HTML 實(shí)體代碼,而是使用 jQuery的 text 函數(shù):
$(‘the-box’).text(‘300 》 200’);
這將把 div 元素更新為以下代碼:
《div id=“the-box”》300 》200《/div》
在上面的例子中,原有內(nèi)容被新內(nèi)容取代。如果只是想把消息添加到文本的后面,該怎么做呢?幸好,jQuery 提供了專門的 append 函數(shù):
$(‘the-box’).append(‘, here goes message’);
將這個(gè)函數(shù)應(yīng)用到初始的 div 元素,div 元素的內(nèi)容就變成下面這樣:
《div id=“the-box”》Message goes here, heregoes message《/div》
除了附加以外,您還可以 “前置” 內(nèi)容,即在已有內(nèi)容的前面而不是末尾插入新內(nèi)容。另外,jQuery 提供了在給定元素之外插入內(nèi)容的函數(shù),不管是在開頭還是在末尾。這類函數(shù)可以替換內(nèi)容、清空內(nèi)容、從 DOM 移除所有元素、克隆元素等等。
除了 DOM 操作函數(shù),JavaScript 框架還包含一些用于以編程方式處理元素樣式和 CSS 類的函數(shù)。例如,假設(shè)您有一個(gè)表,您想要在鼠標(biāo)移到某一行時(shí)高亮顯示該行。您創(chuàng)建了一個(gè)特定的名叫 hover 的 CSS 類,并且您想要將這個(gè)類動(dòng)態(tài)添加到行中。在 YUI 中,可以使用以下代碼檢查行中是否已經(jīng)具有 hover 類,如果已經(jīng)有的話,則刪除它,如果沒有的話,則添加它:
if(row.hasClass(‘hover’)) row.removeClass(‘hover’); elserow.addClass(‘hover’);
而且,大多數(shù) JavaScript 框架都有內(nèi)置的 CSS 操作函數(shù)。
?
實(shí)用函數(shù)
很多 JavaScript 框架提供了大量實(shí)用函數(shù),可使 JavaScript 開發(fā)變得很容易。由于這些函數(shù)非常多,因此本文無法一一介紹。我將只討論大多數(shù)框架都具備的一些比較重要的函數(shù)。
如果您曾經(jīng)使用 JavaScript 處理過數(shù)組,你應(yīng)該熟悉使用 for 循環(huán)來遍歷數(shù)組以處理數(shù)組值。例如,看一下清單 2 的代碼:
清單 2. 遍歷 JavaScript 數(shù)組的傳統(tǒng)方法
var fruit = [‘a(chǎn)pple’, ‘banana’, ‘orange’];for(var i = 0;i 《 fruit.length; i++) { alert(fruit[i]);}
清單 2 中的代碼沒有問題,但有些冗長。大多數(shù) JavaScript 框架包含 each 函數(shù),它會(huì)對數(shù)據(jù)組的每個(gè)元素調(diào)用一個(gè)指定的函數(shù)。使用 MooTools,可以用清單 3 的代碼執(zhí)行與清單 2 相同的操作。
清單 3. 使用 MooTools 中的 each 函數(shù)
?。邸產(chǎn)pple’, ‘banana’, ‘orange’].each(function(item) {alert(item);});
清單 3 中的語法與 Prototype 和 jQuery 中的語法相同,而與 YUI 和 ExtJS 中的語法有細(xì)微差異。然而,當(dāng)用于 hash映射或?qū)ο蠖皇菙?shù)組時(shí),各框架的語法都不一樣。例如在 MooTools 中,將用到清單 4 的代碼:
清單 4. 在 MooTools 中對基于鍵/值對的對象使用 each
var hash = new Hash({name: “Joe Lennon”, email:“[email protected]”});hash.each(function(value, key) { alert(key +“: ” + value);});
在 Prototype 中,將用到清單 5 中的代碼。
清單 5. 在 Prototype 中對基于鍵/值對的對象使用 each
var hash = $H({name: “Joe Lennon”, email:“[email protected]”});hash.each(function(pair) { alert(pair.key +“: ” + pair.value);});
每個(gè)框架都包含很多有用的函數(shù),通常劃分為 String 函數(shù)、Number 函數(shù)、Array 函數(shù)、Hash 函數(shù)、Date 函數(shù)等等。更多信息,請查閱相關(guān) JavaScript 框架的 API 參考資料。
?
事件處理
每個(gè) JavaScript 框架都實(shí)現(xiàn)了跨瀏覽器事件處理支持,鼓勵(lì)您從舊式的內(nèi)聯(lián)事件連接轉(zhuǎn)向一種流線化方法??匆幌虑鍐? 中的 jQuery 示例,其中在 hover 事件中高亮顯示 div 元素。
清單 6. 使用 jQuery 連接 hover Event
$(‘the-box’).hover(function() {$(this).addClass(‘highlight’);}, function() {$(this).removeClass(‘highlight’);});
這是一個(gè)由 jQuery 實(shí)現(xiàn)的特殊事件,請注意它有兩個(gè)函數(shù),觸發(fā) onMouseOver 事件時(shí)調(diào)用第一個(gè),觸發(fā) onMouseOut 事件時(shí)調(diào)用第二個(gè)。這是因?yàn)?hover 沒有標(biāo)準(zhǔn)的 DOM 事件。讓我們查看一個(gè)更典型的事件,例如 click(查看清單 7)。
清單 7. 使用 jQuery 連接 click Event
$(‘the-button’).click(function() { alert(‘You pushed thebutton!’);});
如您所見,本例中只有一個(gè)函數(shù)參數(shù)。jQuery 使用這種方式處理大多數(shù) JavaScript 事件。在 jQuery 中使用事件處理函數(shù)時(shí),上下文變量是指觸發(fā)事件的元素。有些框架并不使用這種處理方式。以 Prototype 為例,清單 8 顯示了用 Prototype 實(shí)現(xiàn)的與清單 7 等價(jià)的代碼。
清單 8. 使用 Prototype 連接 click Event
$(‘the-button’).observe(‘click’, function(e) { alert(‘Youpushed the button!’);});
您首先將注意到?jīng)]有 click 函數(shù),而是使用了 observe 函數(shù),該函數(shù)在引用它自身之前將事件作為參數(shù)。您還可能注意到該函數(shù)的參數(shù) e。這就是指向觸發(fā)事件的元素的上下文變量。為了探究其工作原理,讓我們針對 Prototype 重寫 清單6 的代碼(請看清單 9)。
清單 9. 使用 Prototype 連接 hover Event
$(‘the-box’).observe(‘mouseover’, function(e) { var el =Event.element(e); el.addClassName(‘highlight’);});$(‘the-box’).observe(‘mouseout’,function(e) { var el = Event.element(e); el.removeClassName(‘highlight’);});
與 jQuery 中使用美元符號(hào)函數(shù)獲取上下文變量不同的是,在 Prototype 中需要使用 Event.element() 函數(shù)。并且,您需要對 mouseover 和mouseout 使用不同的函數(shù)。
在閱讀本文的過程中,您也許會(huì)注意到函數(shù)使用內(nèi)聯(lián)方式創(chuàng)建且都沒有命名。這意味著它們無法被重用。Prototype 的 hover 例子展示了如何使用已命名的函數(shù)作為替代方法。如清單 10 所示。
清單 10. Prototype 中改進(jìn)的 hover 例子
function toggleClass(e) { var el = Event.element(e);if(el.hasClassName(‘highlight’)) row.removeClassName(‘highlight’); else row.addClassName(‘highlight’);}$(‘the-box’).observe(‘mouseover’,toggleClass);$(‘the-box’).observe(‘mouseout’, toggleClass);
您會(huì)注意到,這次只定義了一個(gè)函數(shù)供 mouseover 和 mouseout 事件調(diào)用。該函數(shù)會(huì)判斷元素是否已經(jīng)高亮顯示了類名,并根據(jù)查找結(jié)果執(zhí)行添加或刪除。您也許會(huì)注意到 e 參數(shù)是隱式傳遞的。換句話說,不需要在 observe 函數(shù)中以參數(shù)形式顯式傳遞事件。
?
Ajax
使用 JavaScript 框架的另一個(gè)有說服力的理由是標(biāo)準(zhǔn)化的跨瀏覽器 Ajax 請求。Ajax 請求是一個(gè)異步 HTTP 請求,通常發(fā)送給服務(wù)器端腳本,后者返回 XML、JSON、HTML 或普通文本格式的響應(yīng)。大多數(shù) JavaScript 框架都有某種形式的Ajax 對象,以及一個(gè)以參數(shù)形式接受一組選項(xiàng)的請求方法。這些選項(xiàng)通常包含 callback 函數(shù),當(dāng)腳本一接收到來自Web 服務(wù)器的響應(yīng)時(shí),就會(huì)調(diào)用此函數(shù)。讓我們看一下 ExtJS、MooTools 和 Prototype 中的 Ajax 請求的樣子。
首先,看一下典型的 ExtJS Ajax 請求(請看清單 11)。
清單 11. 一個(gè) ExtJS Ajax 請求
Ext.Ajax.request({ url: ‘server_script.php’, params: {name1: ‘value1’, name2: ‘value2’ }, method: ‘POST’, success:function(transport) { alert(transport.responseText); }});
ExtJS 中的 request 方法只有一個(gè)參數(shù),這是一個(gè)包含 url、params、method 和 success 等不同字段的對象。url 字段包含服務(wù)器端腳本的 URL,該腳本將被 Ajax 請求調(diào)用。params 字段本身就是一個(gè)對象,包含有將被傳遞給服務(wù)器端腳本的鍵/值對。method 字段可以取兩個(gè)值:GET 或 POST。它的默認(rèn)值為未定義,但如果請求中有 params,將會(huì)默認(rèn)作為 POST 處理。最后一個(gè)字段 success 是 Web 服務(wù)器返回成功響應(yīng)時(shí)調(diào)用的函數(shù)。在本例中,假設(shè)服務(wù)器端腳本返回普通文本,并且文本會(huì)通過警告框顯示給用戶。
下一步,我們看看同樣的請求在 MooTools 中是什么樣子(請看清單 12)。
清單 12. 一個(gè) MooTools Ajax 請求
new Request({ url: ‘server-script.php’, data: { name1:‘value1’, name2: ‘value2’ }, method: ‘post’, onComplete: function(response) {alert(response); }}).send();
如您所見,MooTools 與 ExtJS 非常相似。你也許注意到,與使用 params 不同,這里使用 data 字段傳遞變量,而且必須使用小寫指定方法。還有,沒有使用 success callback 函數(shù),MooTools 使用了一個(gè) onComplete 函數(shù)。最后,與 ExtJS 不同的是,在 MooTools 中,您需要使用Request 對象的 send() 函數(shù)發(fā)送請求。
最后,讓我們看看 Prototype 中的請求是否具有顯著的不同(請看清單 13)。
清單 13. 一個(gè) Prototype Ajax 請求
new Ajax.Request(‘server-script.php’, { params: { name1:‘value1’, name2: ‘value2’ }, method: ‘post’, onSuccess: function(transport) {alert(transport.responseText); }});
同樣,Prototype 的工作方式基本一致,只是有些句法上的小差別。首先,Prototype 的 Request 對象為其構(gòu)造函數(shù)獲取兩個(gè)參數(shù)。第一個(gè)參數(shù)是請求將被發(fā)送到的 URL,第二個(gè)參數(shù)是一個(gè)具有典型 Ajax 選項(xiàng)的對象,如前兩個(gè)例子所見。當(dāng)然,由于 URL 現(xiàn)在作為單獨(dú)的參數(shù)傳遞,它沒有出現(xiàn)在選項(xiàng)列表中。同樣需要注意,與 MooTools 不同,Prototype Ajax Request 對象構(gòu)造函數(shù)隱式地發(fā)送請求,因此不需要調(diào)用任何方法來實(shí)際發(fā)起 HTTP 請求。
大多數(shù) JavaScript 框架中的 Ajax 支持要比此處演示的內(nèi)容更加深入。重要的增強(qiáng)功能包括使用接收到的響應(yīng)自動(dòng)更新元素,而不需要任何特殊的 onSuccess 函數(shù)。一些庫甚至包含預(yù)建的自動(dòng)完成功能,如您在 Google 搜索引擎中所看到的,在輸入時(shí)會(huì)出現(xiàn)常見搜索項(xiàng)。
在下一節(jié)中,您將學(xué)習(xí) JavaScript 框架為 Web 開發(fā)人員提供的用戶體驗(yàn)(UX)增強(qiáng)功能。
?
UX 增強(qiáng)功能
到目前為止,本文全部關(guān)注的是使用 JavaScript 框架的編程優(yōu)勢以及它們?nèi)绾魏喕换ナ綉?yīng)用程序的編寫。然而,對于大多數(shù)框架,還有另外一個(gè)更吸引人的方面:用戶界面(UI)組件以及用戶體驗(yàn)增強(qiáng),這些在以前需要付出很大努力構(gòu)建的工作現(xiàn)在可以輕松完成。
本節(jié)將分別介紹以下框架的 UX 增強(qiáng)功能:Prototype、jQuery、YUI、ExtJS 和 MooTools。
Prototype
Prototype 是少數(shù)幾個(gè)不提供開箱即用 UI 組件和 UX 增強(qiáng)功能的 JavaScript 框架之一。它將這些內(nèi)容轉(zhuǎn)移到其姊妹庫script.aculo.us(最新版本是 Scripty2)中。Script.aculo.us 還添加了對 Prototype 中的各種效果和行為的支持。包括 highlighting、morphing、folding、shaking、sliding、puffing 等等。Script.aculo.us還提供拖放支持,例如滑塊、in-place Ajax 編輯器和 autocompleters。與其他框架不同,Script.aculo.us 將所有控件(例如滑塊和 autocompleters)的設(shè)計(jì)留給開發(fā)人員,并且未提供標(biāo)準(zhǔn)界面。
jQuery
與 Prototype 不同,jQuery 核心庫中包含一些基本的 UX 增強(qiáng)。這些增強(qiáng)與script.aculo.us 中的一些基本效果類似。例如 sliding 和 fading。然而,要獲得更高級的 UX 特性,就需要下載 jQuery UI 庫,它包含比 jQuery 核心庫更多的效果,以及交互特性,如拖放、調(diào)整大小和排序。與 script.aculo.us 不同的是,jQuery UI 還包含一些小部件或組件,它們使開發(fā)具有吸引力的界面變得更加容易。目前,這些組件包括 Accordion、Datepicker、Dialog、Progressbar、Slider 和 Tabs。這些小部件都可以劃分主題,并且 jQuery UI 包含各種各樣的主題,可以適合您自己的特定 Web 站點(diǎn)或 Web 應(yīng)用程序的組件。圖 1 演示了 jQuery UIDatepicker 小部件的例子,該小部件的主題為 Cupertino。
圖 1. jQuery UI Datepicker Widget
YUI
Prototype 和 jQuery 不包含開箱即用的 UI 小部件,但是 Yahoo! User Interface 庫 (YUI) 包含一個(gè) bucketload。除了支持拖放和調(diào)整大小外,YUI 第二版還包含autocompleters、calendar 控件、carousel 組件、繪圖、對話框、進(jìn)度條、富文本編輯器(所見即所得文本區(qū))、滑塊、選項(xiàng)卡、樹等等。在撰寫本文時(shí),上述小部件都沒有包含在 YUI 第三版中。圖 2 是結(jié)合使用這些組件的一個(gè)例子。
圖 2. 復(fù)雜的 YUI 應(yīng)用程序示例
ExtJS
和 YUI 一樣,ExtJS 包含大量開箱即用的組件,其中有很多功能強(qiáng)大的網(wǎng)格控件,支持內(nèi)聯(lián)編輯、分頁、篩選、分組、匯總、緩沖和數(shù)據(jù)綁定。ExtJS 組件具有非常專業(yè)的外觀,并且分門別類。其他小部件包括選項(xiàng)卡、圖表、窗口(對話框)、樹、布局管理器、增強(qiáng)的表單控件、工具欄和菜單、拖放操作和直接遠(yuǎn)程功能(directremoting)。這僅僅是ExtJS 所提供的一小部分,如果要查找更多 RIA 組件,請登錄 ExtJS 網(wǎng)站。圖 3 是用 ExtJS 開發(fā)的 Web 桌面程序示例,展示了這個(gè)庫的豐富特性。
圖 3. ExtJS 桌面應(yīng)用程序示例
MooTools
MooTools 和 Prototype、jQuery 一樣,也不包含開箱即用的 UI 控件和小部件。與 jQuery 一樣,它包含一組有限的效果。其更高級的 UX 增強(qiáng)包含在MooTools.More.js 擴(kuò)展中。這個(gè)擴(kuò)展不僅僅是簡單的 UX/UI 插件,而是包含對 Array、Date、Hash 和 String 類以及額外的 Element 擴(kuò)展的一些有趣的附加功能。至于 UX 增強(qiáng)功能,MooTools.More.js包含拖放支持以及一些其他效果。這些附加功能還包括一些 UI 控件,如 accordion、可排序的HTML 表、scroller、工具提示和 Ajax spinneroverlays。然而,與 script.aculo.us 一樣,您需要自己設(shè)計(jì)這些控件。
?
Framework 比較
表 1 是本文介紹的五個(gè)框架所含特性的詳細(xì)比較。
表 1. 框架特性比較
其他重要框架
限于篇幅,本文只介紹了五個(gè)比較常見的 JavaScript 框架。這并不表示它們是最流行的、最好的或是功能最多的框架。
其他一些 JavaScript 框架也值得注意,包括:
· Cappuccino
· Dojo
· Glow
· GWT
· MochiKit
· Qooxdoo
· Rialto
· Rico
· SproutCore
· Spry
· UIZE
?
結(jié)束語
本文介紹了JavaScript中原型相關(guān)的概念,對于原型可以歸納出下面一些點(diǎn):
所有的對象都有“[[prototype]]”屬性(通過__proto__訪問),該屬性對應(yīng)對象的原型
所有的函數(shù)對象都有“prototype”屬性,該屬性的值會(huì)被賦值給該函數(shù)創(chuàng)建的對象的“__proto__”屬性
所有的原型對象都有“constructor”屬性,該屬性對應(yīng)創(chuàng)建所有指向該原型的實(shí)例的構(gòu)造函數(shù)
函數(shù)對象和原型對象通過“prototype”和“constructor”屬性進(jìn)行相互關(guān)聯(lián)
還有要強(qiáng)調(diào)的是文章開始的例子,以及通過例子得到的一張“普通對象”,“函數(shù)對象”和“原型對象”之間的關(guān)系圖,當(dāng)你對原型的關(guān)系迷惑的時(shí)候,就想想這張圖(或者重畫一張當(dāng)前對象的關(guān)系圖),就可以理清這里面的復(fù)雜關(guān)系了。
通過這些介紹,相信一定可以對原型有個(gè)清晰的認(rèn)識(shí)。
JavaScript 框架的概念以及它為什么會(huì)給 Web 網(wǎng)站和 Web 應(yīng)用程序開發(fā)帶來好處。本文對這些框架的常見功能作了簡要描述,并舉例說明在較常見的庫中如何實(shí)現(xiàn)這些特性。本文還分別講解了作為這五個(gè)框架的一部分或作為單獨(dú)的附加功能的 UI 和 UX 增強(qiáng)。最后,還提供了一個(gè)直觀的特性對比圖表,其中列出了各個(gè)框架各自具備和缺乏的選項(xiàng)。有了這些知識(shí),您應(yīng)該就可以進(jìn)行更深入的研究,然后明智地選擇適合您及您的開發(fā)團(tuán)隊(duì)的框架。
評論