JavaScript是jQuery應用的基礎,掌握JavaScript這門語言是使用jQuery的基礎條件。本章不會全面細緻的講解JavaScript的所有, 而是講解其精髓,這些知識能夠提高你們的JavaScript內功。切忌,要修煉上乘的武功,必需要有深厚的內功基礎,不然只可學到其招式而發揮不了功力。JavaScript實際上包括三部分:javascript
w ECMAScript 描述了該語言的語法和基本對象。php
w DOM 描述了處理網頁內容的方法和接口。html
w BOM 描述了與瀏覽器進行交互的方法和接口。java
本章將講解ECMAScript和DOM的相關知識。程序員
一般所說的JavaScript語法,其實是指JavaScript中的ECMAScript部分。本節主要講解JavaScript的語法和語意特性。正則表達式
不少人知道JavaScript,殊不知道ECMAScript爲什麼物。這就比如你知道本書的做者叫作張子秋,可是殊不知道做者也屬於人類同樣。爲何要知道二者的關係呢?由於除了JavaScript,還有微軟的JScript,以及Flash中的ActionScript, 這幾種語言寫法上有太多的類似之處。懂得ECMAScript,就可以清楚地理解這些語言爲什麼如此的類似算法
什麼是ECMAScript?下面是維基百科中對於ECMAScript的定義:編程
ECMAScript是一種由ECMA國際(前身爲歐洲計算機制造商協會)經過ECMA-262標準化的腳本程序設計語言。這種語言在萬維網上應用普遍,它每每被稱爲JavaScript或JScript,但實際上後二者是ECMA-262標準的實現和擴展。json
1995年Netscape公司發佈的Netscape Navigator 2.0中,發佈了與Sun聯合開發的JavaScript 1.0而且大獲成功, 而且隨後的3.0版本中發佈了JavaScript1.1,恰巧這時微軟進軍瀏覽器市場,IE 3.0搭載了一個JavaScript的克隆版-JScript, 再加上Cenvi的ScriptEase(也是一種客戶端腳本語言),致使了三種不一樣版本的客戶端腳本語言同時存在。爲了創建語言的標準化,1997年JavaScript 1.1做爲草案提交給歐洲計算機制造商協會(ECMA),第三十九技術委員會(TC39)被委派來「標準化一個通用的,跨平臺的,中立於廠商的腳本語言的語法和語意標準」。最後在Netscape、Sun、微軟、Borland等公司的參與下制訂了ECMA-262,該標準定義了叫作ECMAScript的全新腳本語言。數組
今後之後的Javscript,JScript,ActionScript等腳本語言都是基於ECMAScript標準實現的。
因此,ECMAScript其實是一種腳本在語法和語義上的標準。實際上JavaScript是由ECMAScript,DOM和BOM三者組成的。 因此說,在JavaScript,JScript和ActionScript中聲明變量,操做數組等語法徹底同樣,由於它們都是ECMAScript。可是在操做瀏覽器對象等方面又有各自獨特的方法,這些都是各自語言的擴展。圖2-1顯示了ECMAScript與各個語言的關係。
圖 2-1 各類腳本語言的關係
不一樣於C#,Java中多種多樣的「類」,JavaScript是一個相對單純的世界。JavasScript中也分爲值類型和引用類型兩大類,可是這兩大類中都只含有不多的幾種類型。
瞭解C#語言的都對值類型和引用類型不會陌生。JavaScript中有時也稱爲原始值(primitive value)和引用值(reference value)。
值類型:存儲在棧(stack)中,一個值類型的變量實際上是一個內存地址,地址中存儲的就是值自己。
引用類型:存儲在堆(heap)中,一個引用類型的變量的值是一個指針,指向存儲對象的內存處。
時刻記着「值類型」和「引用類型」的區別有助於更好的理解語言的精髓。爲了化繁爲簡, 雖然從理論上應該分爲「值類型」和「引用類型」,又能夠將JavaScript中對象分爲「本地對象」,「內置對象」和「宿主對象」,可是在實際應用中爲了讓JavaScript變得真正單純,能夠將JavaScript中的類型分爲:
undefined,null,number,string,boolean,function, 其餘object引用類型。
即便一個JavaScript初學者對這些類型也不會陌生。這種分類方法前6種都是最常使用的JavaScript類型,第7種object引用類型其實並非獨立的類型,由於function就是一種引用類型,另外JavaScript中的值類型背後其實也是一個「引用類型」,這一點和C#極其類似,就是全部的類型都是從Object中派生而來。好比number是一個「值類型」, 可是其實存在一個引用類型「Number」,咱們可使用以下的方式聲明一個變量:
var oNumberObject = new Number(55);
Number對象是ECMAScript標準中定義的。可是本文不許備深刻的講解它們,由於最經常使用的仍是使用下面的方式建立一個「值類型」的數值爲55的變量:
var iNumberObject = 55;
這就夠了不是嗎?可是要記住藏在背後的Number對象是一個引用類型!
若是你對undefined和null這兩種類型常常分辨不清,那麼恭喜,由於你會找到不少的知音。其實要理解這兩種類型, 首先要知道它們設計的初衷:
undefined:表示一個對象沒有被定義或者沒有被初始化。
null:表示一個還沒有存在的對象的佔位符。
有意思的是undefined類型是從null派生來的。因此它們是相等的:
alert(null == undefined); //輸出 「true」
對於全部的JavaScript開發人員,最常碰到的就是對象不存在錯誤。正如在C#中的空引用錯誤同樣。不少程序員習慣的覺得JavaScript中的if會自動將undefined和null對象轉化爲false,好比:
var oTemp = null;
if(oTemp){}; //false
if(undefined){}; //false
上面的語句都是正確的,if中的條件都是false。可是若是註釋掉oTemp的聲明部分,狀況就不一樣了:
//var oTemp = null; 註釋掉變量聲明語句
if(oTemp){}; //error
會拋出錯誤。可是不管是否聲明過oTemp對象,使用typeof運算符獲取到的都是undefined而且不會報錯:
//var oTemp1; 註釋掉變量聲明語句
alert(typeof oTemp1); //輸出 「undefined」
var oTemp2;
alert(typeof oTemp2); //輸出 「undefined」
因此若是在程序中使用一個可能沒有定義過的變量,而且沒有使用typeof作判斷,那麼就會出現腳本錯誤。而若是是此變量是null或者沒有初始化的undefined對象,能夠經過if或者「==」來判斷。切記,未聲明的對象只能使用typeof運算符來判斷!
正由於如此,typeof常常和undefined變量一塊兒使用。typeof運算符返回的都是一個字符串,而時常程序員會看成類型來使用。是否你也犯過以下的錯誤呢?
//var oTemp; 註釋掉變量聲明語句
if(typeof oTemp == undefined ){…}; //false
這裏if將永遠是false。要時刻銘記typeof返回的是字符串,應該使用字符串比較:
//var oTemp; 註釋掉變量聲明語句
if(typeof oTemp ==」undefined」){…};//true
下面是typeof運算符對各種型的返回結果:
w undefined:「undefined」
w null:「object」
w string:「string」
w number:「number」
w boolean:「Boolean」
w function:「function」
w object:「object」
結果只有null類型讓人吃驚。null類型返回object,這實際上是JavaScript最初實現的一個錯誤,而後被ECMAScript沿用了,也就成爲了如今的標準。因此須要將null類型理解爲「對象的佔位符」,就能夠解釋這一矛盾,雖然這只是一中 「辯解」。對於代碼編寫者必定要時刻警戒這個「語言特性」,由於:
alert(typeof null == 「null」);//輸出 false
永遠爲false。
還要提醒,一個沒有返回值的function(或者直接return返回)實際上返回的是undefined。
function voidMethod()
{
return;
}
alert(voidMethod()); //輸出 "undefined"
由於JavaScript是弱類型語言,因此在變量的聲明上體現了與C#等強類型語言的明顯不一樣。
JavaScript可使用var顯式的聲明變量:
var iNum;
var sName;
也能夠在一個var語句中聲明多個變量,用「,」分割變量:
var iNum, sName;
變量的類型是在賦值語句中肯定的,JavaScript使用「=」賦值,能夠在聲明變量的同時對其進行賦值:
var sName=」ziqiu.zhang」;
由於是弱類型語言,即便變量的類型在初始化時已經被肯定,仍然能夠在以後把它設置成其它類型,好比:
var sName = 「ziqiu.zhang」;
alert(sName); //輸出 「ziqiu.zhang」
sName = 55;
alert(sName); //輸出 輸出 「55」
變量除了能夠顯式聲明,也能夠隱式聲明。所謂隱式聲明,就是不使用var關鍵詞聲明,而直接爲變量賦值,好比:
//var sName; 註釋掉變量聲明語句
sName = 「ziqiu.zhang」
alert(sName); //輸出 輸出 「ziqiu.zhang」
上面的語句不會出任何的錯誤。就如同使用var聲明過變量同樣。可是不一樣之處是變量的做用域。隱式聲明的變量老是被建立爲全局變量。即便是在一個函數中建立的變量也依然是全局的。好比:
function test()
{
sName = " ziqiu.zhang ";
}
//var sName;
test();
alert(sName); //輸出 輸出 「ziqiu.zhang」
雖然sName是在函數中建立的,可是在函數外層仍然可以訪問sName,由於sName是全局變量。
變量能夠隱式聲明,可是不能夠不聲明。若是一個變量既沒有隱式聲明,也沒有顯式聲明,那麼在使用時會發生對象未定義的錯誤。
由於JavaScript語言的靈活性,不少人會忽視變量的命名,有的公司即便制定了後段代碼如C#的命名規範,卻忽視了JavaScript變量的命名規範。
從一開始就制定完整的JavaScript命名規範是頗有必要的,尤爲是在愈來愈追求用戶體驗的今天,JavaScript將會承載愈來愈多的用戶邏輯。
先來學習三種命名方法:
w Camel命名法:首字母小寫,接下來的每一個單詞首字母大寫。好比:
var firstName, var myColor;
w Pascal命名法:首字母大寫,接下來的每一個單詞首字母大寫。好比:
var FirstName, var MyColor;
w 匈牙利類型命名法:在以Pascal命名法的變量前附加一個小寫字母來講明該變量的類型。例如s表示字符串,則聲明一個字符串類型的變量爲:
var sFirstName;
在JavaScript中應該使用匈牙利命名法命名變量,使用Camel命名法命名函數。
這一點是和C#、Java不一樣的。一般服務器端語言都是用Pascal命名法命名方法,即首字母要大些。可是JavaScript中的全部方法首字母都是小寫的,爲了和JavaScript中的默認命名一致,因此要採用Camel命名法命名方法。好比:
function testMethod(){};
雖然JavaScript中的變量能夠變換類型,可是熟悉強類型語言的人都知道,這是很危險的作法,頗有可能最後在使用時都沒法肯定變量的類型。因此應該儘可能使用匈牙利命名法命名變量,下面是匈牙利命名法的前綴列表:
類型 |
前綴 |
示例 |
Array |
a |
aNameList |
Boolean |
b |
bVisible |
Float |
f |
fMoney |
Function |
fn |
fnMethod |
Int |
i |
iAge |
Object |
o |
oType |
Regexp(正則表達式) |
re |
rePattern |
string |
s |
sName |
可變類型 |
v |
vObj |
表 2-1 JavaScript中的匈牙利命名法前綴
使用匈牙利命名法是推薦的作法。可是不少程序由於歷史遺留緣由都使用Camel命名法。記住匈牙利命名法纔是正確的方法。寫代碼也是一門藝術,只要有機會就應該作正確的事。
變量的做用域就是變量做用的範圍,只有在變量的做用域內才能夠訪問該變量,不然是沒法訪問此變量的。好比:
function test()
{
var sName = " ziqiu.zhang ";
}
alert(sName); //輸出 "sName未定義";
會提示錯誤」sName」未定義。
JavaScript變量的做用域基本上與C#、Java等語言相同。可是由於JavaScript語言的特殊性,有些須要特殊理解的地方。
首先要了解,全局變量是Window對象的屬性。
前面已經提到過隱式聲明的變量都是全局變量。說是「全局」但實際上仍是有做用域的,那就是當前窗口window對象。一個全局變量就是window對象的一個屬性:
function test()
{
sName = " ziqiu.zhang ";
}
//var sName;
test();
alert(sName);
alert(window.sName);
隱式聲明瞭一個全局變量sName後,既能夠直接訪問sName,也能夠經過window.sName訪問,二者的效果是相同的。
若是說全局變量仍是傳統的做用域模型,那麼閉包(closure)的概念是會讓初學者迷惑的。閉包的概念比較難以理解, 先看閉包的定義:
閉包是一個擁有許多變量和綁定了這些變量的環境的表達式(一般是一個函數),於是這些變量也是該表達式的一部分。
簡單表述:
閉包就是function實例以及執行function實例時來自環境的變量。
不管是定義仍是簡單表述,都讓人難以理解。由於這都是理論的抽象。經過實例就能夠快速的理解閉包的含義:
【代碼路徑:jQueryStorm.Web/chapter2/closure.aspx】
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title> jQuery storm - 閉包舉例</title>
</head>
<body>
<div id="divResult">
</div>
<script type="text/JavaScript">
function start()
{
var count = 10;
//設置定時器,每隔3秒鐘執行一次
window.setInterval(function()
{
//設置定時器,每隔3秒鐘執行一次
document.getElementById("divResult").innerHTML += count + "<br/>";
count++;
}, 3000);
};
start();
</script>
</body>
上面的實例使用setInterval函數,設置了一個定時器,每3秒鐘會向divResult容器中,添加count變量的值,而且count變量自增。
count是start函數體內的變量,按照一般的理解count的做用域是在start()函數內,在調用start()函數結束後應該也會消失。可是此示例的結果是count變量會一直存在,而且每次被加1,數據結果是:
10
11
12
13
這是由於count變量是setInterval中建立的匿名函數(也就是包含count++的函數)的閉包的一部分!
再通俗的講, 閉包首先就是函數自己,好比上面這個匿名函數自己,同時加上在這個函數運行時須要用到的count變量。
JavaScript中的閉包是隱式的建立的,而不像其餘支持閉包的語言那樣須要顯式建立。在C#語言中不多碰到是由於C#中沒法在方法中再次聲明方法. 而在一個方法中調用另外一個方法一般使用參數傳遞數據。
JavaScript中的閉包是很是強大的,能夠用於執行復雜的邏輯。可是在使用時要時刻當心,由於閉包的強大也致使了它的複雜,使用閉包會讓程序難以理解和維護。
function類型是JavaScript的靈魂,由於程序是由數據和邏輯兩部分組成的,基本的數據類型能夠存儲各類數據,而function則用來存儲邏輯。固然這只是將function看成「方法」來看待而已。實際上function類型有着更增強大的生命力。
可使用function聲明一個方法,好比:
function testMethod()
{
alert("Hello world!");
}
testMethod(); //輸出 "Hello world!"
調用該方法將顯示「Hello world!」。然而除了方法,function還能夠用來聲明「類型」。JavaScript中本沒有「類型」的概念,也就是C#中的Class的概念,可是可使用function來假裝一個類型。好比:
function Car()
{
this.color = "none";
if (typeof Car._initialized == "undefined")
{
Car.prototype.showColor = function()
{
alert(this.color);
}
}
Car._initialized = true;
};
上面的代碼聲明瞭一個Car類,而且帶有一個color屬性和一個showColor方法。這裏使用的是「動態原型」方法,此方法將在下一節中作詳細講解。
好了,如今可使用「new」運算符建立一個類的實例,並使用它:
var car = new Car();
car.showColor(); //輸出 "none"
car.color = "blue";
car.showColor(); //輸出 "blue"
雖然function有這麼多的能力,可是其本質卻很簡單:
function變量是引用類型,內容存儲的是指向function內容的指針,function的內容就是函數體。
JavaScript中的對象有一個特色,裏面存儲的都是name/value(名/值)。用name/value存儲屬性是比較容易理解的,一個屬性的名稱就是name,好比color屬性。屬性的值就是value,好比color的屬性值red。這與C#語言是同樣的。可是JavaScript中的function也是一個name/value,這一點十分獨特。可使用alert方法輸出function變量的內容:
alert(testMethod);
輸出:
圖 2-2 函數變量輸出結果
發現輸出的內容就是testMethod的方法體自己。如同一個屬性輸出的是其屬性值。
認清這一點有助於更好的理解function的本質。
function類型的對象,配合new運算法,能夠建立一個實例。依然是上面建立一個Car類實例的例子:
var car = new Car();
首字母大寫的Car是一個函數,可是使用new運算符生成的是一個object:
alert(typeof car); //輸出 object
而Car函數自己則更像是一個類的構造函數。實際上,new與運算符的操做,等價於下面的語句:
var car2 = {}; //建議一個空對象
//將car2的原型設置爲Car.prototype,這一部是經過javascript內部的Object.create實現的,可是此函數是內部函數沒法直接訪問。
Car.call(car2); //修改函數調用的上下文
alert(car2.color);
其中第二步沒法直接用語句替代,是由於實現這一步是在javascript引擎內部,方法沒法直接調用。
JavaScript不支持方法的重載,緣由是在JavaScript中同名的function只能有一個,而且function函數的參數個數可使任意的。
雖然沒法直接支持「重載」,可是能夠經過arguments對象來假裝重載,讓一個function根據參數的不一樣實現不一樣的功能。
arguments是在function中的特殊對象,經過arguments對象能夠訪問到function調用者傳遞的全部參數信息,好比獲取到傳入的參數個數:
function testMethod()
{
alert(arguments.length);
}
testMethod(); //輸出 "0"
testMethod("abc"); //輸出 "1"
能夠經過arguments的索引arguments[index]獲取到每個傳入的參數值:
function myMethod()
{
alert(arguments instanceof Array);
if (arguments.length == 0)
{
alert("no arguments");
}
else if (arguments.length == 1)
{
alert("Hello:" + arguments[0].toString())
}
}
myMethod();
myMethod("ziqiu.zhang");
顯然,上面的方法經過判斷參數的個數實現了假裝重載。可是這種實現並很差,由於全部的重載邏輯都集中在一個方法中,而且有使人厭煩的多個if-else分支。
可是使用arguments能夠開發出功能強大靈活的函數。在開發jQuery的插件時就要常用arguments對象實現函數的僞重載。
在C#中,this變量一般指類的當前實例。JavaScript則不一樣,JavaScript中的「this」是函數上下文,,不是在聲明時決定的,而是在調用時決定的。由於全局函數其實就是window的屬性,因此在頂層調用全局函數時的this是指window對象,下面的例子能夠很好的說明這一切:
<script type="text/JavaScript">
var oName = { name: "ziqiu.zhang" };
window.name = "I am window";
//showName是一個函數,用來顯示對象的name屬性
function showName()
{
alert(this.name);
}
oName.show = showName;
window.show = showName;
showName(); //輸出 輸出爲 "I am window"
oName.show(); //輸出 輸出爲 "ziqiu.zhang"
window.show(); //輸出 輸出爲 "I am window"
</script>
showName是一個function,使用alert語句輸出this.Name,。由於showName是一個全局的函數,因此其實showName是window 的一個屬性:window.showName,調用全局的showName就是調用window.showName, 由於window上的name屬性爲」I am window」,因此直接調用showName和調用window.showName都會輸出」I am window」。
oName是一個對象,前面已經講過函數也是一個屬性,能夠像屬性同樣賦值,因此將showName方法複製給oName的show方法,當調用oName.show方法時,也會觸發alert(this.name)語句,可是此時方法的調用者是oName,因此this.name輸出的是oName的name屬性。
利用「this指向函數調用者」的特性,能夠實現鏈式調用。jQuery中大部分都是鏈式調用。首先實現一個鏈式調用的例子:
var oName = { name: "ziqiu.zhang", age:999 };
oName.showName = function()
{
alert(this.name);
return this;
};
oName.showAge = function()
{
alert(this.age);
return this;
};
oName.showName().showAge();
上面的代碼首先輸出」ziqiu.zhang」,而後輸出」999」。oName使用鏈式調用方法分別調用了兩個方法,等效於:
oName.showName();
oName.showAge();
使用鏈式調用的關鍵點就是要返回調用者自己,也就是this指針。
在使用this時,也經常由於this指向函數上下文的特性,致使引用的錯誤,好比:
var myClass =
{
checkName: function() { return true; },
test: function() {
if (this.checkName()) {
alert("ZZQ");
}
}
}
$("#btnTest").click(myClass.test);
上面的例子中試圖使用面向對象的方式將一些方法封裝在myClass中,併爲btnTest對象添加單擊事件的事件處理函數。如今若是單擊btnTest調用myClass.test方法,會發生錯誤,提示找不到checkName方法。jQuery.proxy函數解決此問題。參見「jQuery工具函數」一章中的「修改函數上下文」一節。
JavaScript中的原型(Prototype)是JavaScript最特別的地方之一。不管是實現JavaScript的面向對象仍是繼承,使用prototype都必不可少。
「原型」表示對象的原始狀態,JavaScript中的每一個對象都有一個prototype屬性,可是隻有Function類型的prototype屬性可使用腳本直接操做。Object的prototype屬於內部屬性,沒法直接操做。prototype屬性自己是一個object類型。一個函數的prototype上全部定義的屬性和方法,都會在其實例對象上存在。因此說prototype就是C#類中的實例方法和實例屬性。實例方法和靜態方法是不一樣的,靜態方法是指不須要聲明類的實例就可使用的方法,實例方法是指必需要先使用"new"關鍵字聲明一個類的實例, 而後才能夠經過此實例訪問的方法。
下面是兩種方法的聲明方式:
function staticClass() { }; //聲明一個類
staticClass.staticMethod = function() { alert("static method") }; //建立一個靜態方法
staticClass.prototype.instanceMethod = function() { "instance method" }; //建立一個實例方法
上面首先聲明瞭一個類staticClass, 接着爲其添加了一個靜態方法staticMethod 和一個動態方法instanceMethod,區別就在於添加動態方法要使用prototype原型屬性。
對於靜態方法能夠直接調用:
staticClass.staticMethod();
可是實例方法不能直接調用:
staticClass.instanceMethod(); //語句錯誤, 沒法運行.
實例方法須要首先實例化後才能調用:
var instance = new staticClass();//首先實例化
instance.instanceMethod(); //在實例上能夠調用實例方法
使用prototype除了能夠聲明實例方法,也能夠聲明實例屬性。正由於原型有着如此強大的功能,因此可以使用原型來實現JavaScript的面向對象。目前存在着不少以面向對象的形式建立對象的方法,本書不一一詳細講解,只介紹一種最經常使用,最容易理解和使用的方法:動態原型方法。
假設要定義一個汽車Car類型, 具備屬性color和方法showColor(),則可使用下面的方式聲明和使用:
function Car()
{
//聲明屬性
this.color = "none";
//聲明方法
if (typeof Car._initialized == "undefined")
{
Car.prototype.showColor = function()
{
alert(this.color);
}
}
Car._initialized = true;
};
var car = new Car();
car.showColor(); //輸出 輸出爲 "none"
car.color = "blue";
car.showColor(); //輸出 輸出爲 "blue"
動態原型方法的精髓在於使用prototype聲明實例方法,使用this聲明實例屬性。除了更加接近面向對象的編程方式,這種方式特色就是簡單易懂。注意充當「類定義」的function並無帶有任何參數,雖然也能夠傳遞參數進去,可是不要這麼作。這是爲了下一節講解的使用原型鏈實現繼承機制而做的準備。
能夠在創立了對象之後再爲其屬性賦值。雖然麻煩了一點,可是代碼簡單,易於理解。
除了面向對象的聲明方式,在面向對象的世界中最常使用的就是對象繼承。在JavaScript中能夠經過prototype來實現對象的繼承。繼續上一節Car的例子。假設Car有兩個派生類,一個是GoodCar,特色是跑得快。一個是BadCar,特色是跑得慢。可是它們和Car同樣都具備color屬性和showColor方法。
以GoodCar類爲例,只要讓GoodCar的prototype屬性爲Car類的一個實例,便可實現類型的繼承:
GoodCar.prototype = new Car();
如今,GoodCar類已經有了Car的全部屬性和方法:
var goodCar = new GoodCar();
goodCar.showColor(); //輸出 "none"
實現了繼承後,GoodCar還要實現本身的run()方法,一樣使用prototype實現,下面是GoodCard類的完整定義:
//建立GoodCar類
function GoodCar()
{ }
GoodCar.prototype = new Car();
GoodCar.prototype.run = function() { alert("run fast"); }
須要注意GoodCar類自身的方法必定要在實現繼承語句以後定義。
爲什麼稱這種方式爲原型鏈繼承呢?由於GoodCar的prototype是Car,Car的prototype是object,也就是說GoodCar也具備object對象全部的屬性和方法。這是一個「鏈」的結構。
使用原型能夠簡單快捷的實現JavaScript的面向對象和繼承。
DOM太經常使用了,以致於不少人操做了DOM殊不知道使用的是DOM。從概念上區分JavaScript核心語法,DOM和BOM是頗有必要的,「學院派」做風可以讓Web的知識體系結構更加清晰。
DOM(文檔對象模型)是 HTML 和 XML 的應用程序接口(API)。
上面是DOM的定義。有人將操做HTML理解爲操做DOM,這沒有錯。可是HTML並非DOM。操做HTML也不是DOM的惟一功能。DOM是一種模型以及操做這些模型的API。DOM分爲不一樣的部分和級別。按照部分來劃分,DOM包括:
w Core DOM
定義了一套標準的針對任何結構化文檔的對象
w XML DOM
定義了一套標準的針對 XML 文檔的對象
w HTML DOM
定義了一套標準的針對 HTML 文檔的對象。
因此Web開發人員常說的DOM實際上是指HTML DOM。這其實只是DOM的一部分。DOM標準分爲level 一、二、3,每一個標準都規定了不一樣的功能,並非全部的瀏覽器都能實現最高級別的DOM標準。Firefox是目前實現DOM標準最優秀的瀏覽器,但仍在爲了實現所有的DOM標準而努力。本書不對DOM作更細緻的講解,本節的目的讓開發人員正確的理解DOM與 HTML DOM的關係。
經過JavaScript,能夠經過DOM操做HTML的各個元素。現在的頁面標準已是XHTML時代。可是頁面的本質依然是XML,一種特殊的用於頁面顯示的XML。不管是ASP.NET,JavaScriptp仍是php,任何的服務器頁面最後都要輸出爲HTML頁面—瀏覽器只認識HTML。
一個頁面會首先被瀏覽器解析成DOM,好比下面就是一個頁面的DOM結構:
圖 2-3 頁面的文檔對象模型
頁面被瀏覽器解析爲DOM模型後,經過JavaScript操做頁面元素其實是經過瀏覽器提供的基於DOM的API方法實現的。理解了這些就不會提出相似「DOM元素是JavaScript變量嗎?」等疑問了。
既然DOM是一個文檔樹,就必定會有根節點:html元素。頁面的document對象表明文檔,documentElement屬性就表示html對象:
var oHtml = document.documentElement;
DOM核心的API方法能夠獲取到一個節點的第一個元素和最後一個元素,由於一個html對象一般只有head和body兩個元素,因此能夠經過下面的方式獲取:
var oHead = oHtml.firstChild;
var oBody = oHtml.lastChild;
理解了樹狀結構,使用DOM核心的方法就能夠獲取到頁面上任意的一個元素。下面是最經常使用的獲取元素節點的方法:
(1)getElementById
根據元素id獲取元素。好比獲取id爲divMsg的元素:
document.getElementById("divMsg")
這是最常使用的獲取元素的方法,一個頁面首先應該保證每一個元素的id是惟一的。由於任何元素均可以添加id屬性,因此經過id可以獲取到頁面上面的任何一個元素。這也是效率最高的一個方法。即便使用jQuery功能強大的選擇器,也應該首選使用id選擇器。
可是須要注意getElementById方法只能經過document對象調用,使用oBody. getElementById會引起錯誤。
(2)getElementsByName
經過name屬性獲取一組元素。方法的名字中是複數形式的「Elements」,說明了獲取到的是一組元素,經過Element的單複數形式區分獲取到的是單個對象仍是多個對象,是一種便於記憶的方法。getElementsByName不常使用,由於一般只有表單元素才帶有name屬性。
(3)getElementsByTagName
經過html元素的名稱獲取一組元素。好比頁面上全部的div:
var aDiv = document.getElementsByTagName("div");
也能夠在一個元素上調用getElementsByTagName方法,獲取到的是這個元素內的元素:
var oHtml = document.documentElement;
var oBody = oHtml.lastChild;
var aMyDiv = oBody.getElementsByTagName("div");
上面的語句會得到body元素下全部的div元素。由於全部div都是在body中的,因此其實和document.getElementsByTagName("div") 的結果是相同的。
除了獲取,還有設置屬性,建立元素,附加元素等各類DOM方法。DOM的系統學習不在本書的範圍內,由於隨後的章節將介紹使用jQuery來控制頁面元素,jQuery的內部也是使用DOM實現的,理解了DOM的原理有助於更好的學習jQuery,甚至發現jQuery內部未被發現的錯誤。
經過DOM接口操做HTML元素,實際上並非直接改變HTML代碼,中間有瀏覽器的幫助。在JavaScript中操做的都是DOM元素,可使用JavaScript動態的建立一個div,而後附加到body中:
【代碼路徑:jQueryStorm.Web/chapter2/DOM.aspx】
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>jQuery Storm - DOM</title>
<script type="text/JavaScript">
function onLoad()
{
//獲取到HTML對象
var oHtml = document.documentElement;
//獲取到Body對象
var oBody = oHtml.lastChild;
//建立一個div對象
var oDiv = document.createElement("div");
//建立一個文本節點
var oText = document.createTextNode("Hello DOM");
//將文本節點放到div對象中
oDiv.appendChild(oText);
//將div對象(帶有文本節點)放到body對象中
oBody.appendChild(oDiv);
}
</script>
</head>
<body onload="onLoad()">
</body>
</html>
上面的例子使用了標準的DOM方法建立了div元素節點oDiv,以及一個內容節點oText。在DOM中內容也是一個節點。
注意,若是要改變頁面的DOM結構,應該在瀏覽器加載完頁面的DOM結構以後進行。不然在IE6中會常常出現瀏覽器操做終止的錯誤。本實例將添加元素的工做放在了body的onload事件中。使用jQuery則會有更好更簡單的處理方式,使用$(function(){…})這種形式就能夠在DOM加載完畢後執行函數內的語句。在jQuery核心一節會詳細講解。
動態的添加了一個DOM元素,實際上改變了頁面的DOM模型,隨後瀏覽器會從新解析DOM模型並轉換成頁面顯示,因而頁面上出現了「Hello DOM」,就好像一開始就包含在HTML代碼中同樣。
不少時候就是在操做HTML元素,爲什麼本節要刻意的強調DOM元素與HTML元素呢?這是爲了引出關於DOM屬性與HTML屬性的區別。顧名思義,DOM屬性就是DOM元素的屬性,HTML屬性就是HTML元素的屬性。DOM是沒法直接經過源代碼看到的。可是HTML元素是實實在在存在於頁面的HTML代碼中的,好比:
<img src="images/image.1.jpg" id="hibiscus" alt="Hibiscus" class="classA" />
這是一個HTML元素,圖片的地址就是img元素的src屬性,值爲「images/image.1.jpg」,由於HTML元素最後都要轉化成DOM元素供瀏覽器顯示,因此瀏覽器最後將這部分代碼解析成一個DOM元素,而且也具備一個src屬性,只是屬性值變成了「http://localhost/images/image.1.jpg」(假設網頁在localhost站點下)。HTML中的元素最後會映射成DOM元素,並且中間的轉化並非徹底一致的,img元素的圖片地址會轉化爲絕對路徑,還有些屬性好比「class」轉化爲DOM會後會改變屬性名稱,變成了「className」。
牢記, 在JavaScript中能夠直接獲取或設置"DOM屬性",因此若是要設置元素的CSS樣式類, 要使用的是DOM屬性「className」而不是HTML元素「class」:
document.getElementById("img1").className = "classB";
在執行完上面的語句後,id爲img1的元素已經應用了classB樣式,可是查看網頁源代碼,發現HTML元素的class仍然是classA。
有了jQuery,就不須要Web開發人員刻意的理解class和className的區別。有關使用jQuery操做樣式和屬性將在後面的章節中詳細講解。
要講解JavaScript的所有,須要厚厚的一本書。既然沒法全面講解,就只能抽取一些武功祕籍傳授給你們。除了上面講解的JavaScript知識,本節再介紹一些JavaScript的關鍵知識點。
JSON是指JavaScript Object Notation, 即JavaScript對象表示法. 因此說JSON實際上是一種數據格式,由於JavaScript原生的支持因此賦予了JSON強大的生命力。好比可使用下面的語句建立一個對象:
var oPerson = {
name: "ziqiu.zhang",
age: 999,
school:
{
college: "BITI",
"high school" : "No.18"
},
like: ["mm"]
};
JSON的語法格式是使用"{"和"}"表示一個對象, 使用"屬性名稱:值"的格式來建立屬性, 多個屬性用","隔開.
上例中school屬性又是一個對象. like屬性是一個數組. 使用JSON格式的字符串建立完對象後, 就能夠用"."或者索引的形式訪問屬性:
objectA.school["high school"];
objectA.like[1];
JSON常常在Ajax中使用。讓服務器端頁面只返回JSON格式的數據,使用JavaScript的eval方法將JSON格式的數據轉換成對象,以便使用JavaScript已經操做。
JavaScript原生的支持了JSON格式,而各類語言也都有支持JSON格式的類庫。在.net framework 3.5中微軟已經提供了原生的JSON序列化器。JSON是目前客戶端與服務器端交互數據的最好的數據格式。下面提供一個以.NetFramework3.5版本中自帶的JSON序列化器爲基礎開發的JSON幫助類,代碼也能夠在本書光盤的代碼中或者網站上找到:
【代碼路徑:jQueryStorm.Common.Utility/JsonHelper.cs】
/****************************************************************************************************
* *
* * File Name : JsonHelper.cs
* * Creator : ziqiu.zhang
* * Create Time : 2008-10-8
* * Functional Description : JSON幫助類。用於將對象轉換爲Json格式的字符串,或者將Json的字符串轉化爲對象。
* 使用限制:
* (1)只能在.NET Framework3.5及以上版本使用。
* (2)對象類型須要支持序列化,類上添加[DataContract],屬性上添加[DataMember]
* 使用舉例請參見單元測試項目。
*
* * Remark :
* *
* * Copyright (c) eLong Corporation. All rights reserved.
* ****************************************************************************************************/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization.Json;
using System.IO;
namespace jQueryStorm.Common.Utility
{
/// <summary>
/// JSON幫助類。用於將對象轉換爲Json格式的字符串,或者將Json的字符串轉化爲對象。
/// 只能在.NET Framework3.5及以上版本使用。
/// </summary>
public class JsonHelper
{
/// <summary>
/// 將對象轉化爲Json字符串
/// </summary>
/// <typeparam name="T">源類型</typeparam>
/// <param name="instance">源類型實例</param>
/// <returns>Json字符串</returns>
public static string ObjectToJson<T>(T instance)
{
string result = string.Empty;
//建立指定類型的Json序列化器
DataContractJsonSerializer jsonSerializer = new DataContractJsonSerializer(typeof(T));
//將對象的序列化爲Json格式的Stream
MemoryStream stream = new MemoryStream();
jsonSerializer.WriteObject(stream, instance);
//讀取Stream
stream.Position = 0;
StreamReader sr = new StreamReader(stream);
result = sr.ReadToEnd();
return result;
}
/// <summary>
/// 將Json字符串轉化爲對象
/// </summary>
/// <typeparam name="T">目標類型</typeparam>
/// <param name="jsonString">Json字符串</param>
/// <returns>目標類型的一個實例</returns>
public static T JsonToObject<T>(string jsonString)
{
T result;
//建立指定類型的Json序列化器
DataContractJsonSerializer jsonSerializer = new DataContractJsonSerializer(typeof(T));
//將Json字符串放入Stream中
byte[] jsonBytes = new UTF8Encoding().GetBytes(jsonString);
MemoryStream stream = new MemoryStream(jsonBytes);
//使用Json序列化器將Stream轉化爲對象
result = (T)jsonSerializer.ReadObject(stream);
return result;
}
}
}
在使用時,假設有一個MyClass的類:
public class MyClass
{
public string MyName
{ get; set; }
public int Age
{ get; set; }
}
使用JsonHelper的兩個泛型方法ObjectToJson和JsonToObject,就能夠在對象實例和JSON字符串之間序列化和反序列化:
//建立一個類實例
MyClass myClass = new MyClass() { MyName = "ziqiu.zhang", Age = 99 };
//將對象實例轉化爲JSON字符串
string jsonString = JsonHelper.ObjectToJson < MyClass > (myClass);
//將JSON字符串轉化爲對象實例
myClass = JsonHelper.JsonToObject<MyClass>(jsonString);
有關服務器端程序的序列化和反序列化,還有許多種實現方式。JsonHelper類的實現僅供參考。
使用eval方法能夠將JSON格式的字符轉化爲JavaScript對象:
var sJson = "{ name:'ziqiu.zhang' }";
eval(" var oName =" + sJson);
alert(oName.name); //輸出 「ziqiu.zhang」
注意這裏的sJson對象存儲的是JSON格式的字符串,這個時侯字符串的內容尚未被解析成對象。使用eval方法能夠將sJson字符串轉化爲對象存儲在oName對象中。
eval 函數可計算某個字符串,並執行其中的的 JavaScript 代碼。這讓JavaScript搖身一變成爲了動態語言,能夠在運行時構造語句,經過eval執行,就像上面的解析JSON字符同樣。
eval函數是有返回值的:
var iNum = eval("5+2");
alert(iNum); //輸出 "7"
eval強大的功能讓JavaScript開發人員能夠發揮無窮的想象力。實如今一些高級語言中沒法實現或者實現起來很困難的功能。要知道在C#中使用表達式目錄樹實現動態功能是多麼「深奧」的一件事情。
由於邏輯運算符太經常使用太普通了,因此不少的程序員會認爲無非就是NOT、AND、OR三個運算符,返回布爾值。可是在JavaScript中卻不只僅如此,AND和OR還會返回對象。
(1)NOT運算符
NOT運算符用「!」表示,與邏輯 OR 和邏輯 AND 運算符不一樣的是,邏輯 NOT 運算符返回的必定是 Boolean 值。與不少程序不一樣之處在於,NOT運算符不只僅能夠運算Boolean類型的對象,任何的定義了的對象均可以進行「!」運算。這裏的定義了的對象主要是爲了排除「未定義的undifined」對象,由於即便對象的類型是undifined,也有兩種狀況,一種是未定義,一種是未初始化。未初始化的undifined類型的對象是能夠參與OR、AND和NOT邏輯運算的,只有未定義的undifined對象在邏輯運算中會出現腳本錯誤。這一點目前在一些權威教程的網站上面的解釋都是有錯誤的,請你們尤爲注意。
NOT運算符的規則以下:
w 若是運算數是對象,返回 false
w 若是運算數是數字 0,返回 true
w 若是運算數是 0 之外的任何數字,返回 false
w 若是運算數是 null,返回 true
w 若是運算數是 NaN,返回 true
w 若是運算數是 未初始化的undefined,返回true
w 若是運算數是未定義的undefined,發生錯誤
NOT運算符其實和if條件語句的行爲是同樣的,只是結果相反。
(2)AND運算符
AND 運算符用雙和號(&&)表示。AND運算符的運算數若是都是Boolean類型的對象,那麼運算規則就是若是有一個運算對象是false,則返回false。
JavaScript中的AND與NOT運算符最特別的地方是運算數不必定是Boolean類型,返回的也不必定是 Boolean 值,可能返回對象。
AND運算符的規則以下:
若是一個運算數是對象,另外一個是 Boolean 值,返回該對象。
w 若是兩個運算數都是對象,返回第二個對象。
w 若是某個運算數是 null,返回 null。
w 若是某個運算數是 NaN,返回 NaN。
w 若是某個運算數是未初始化的 undefined,返回undefined。
w 若是運算數是未定義的undefined,發生錯誤
雖然上面描述了AND運算符的行爲,可是在應用時經常會碰到下面的疑惑:
alert(false && null); //輸出 "false"
alert(true && null); //輸出 "null"
alert(null && false);//輸出 "null"
alert(null && true);//輸出 "null"
由於有一個對象是「null」,因此天然想到要應用規則「若是某個元素是null,返回null」,可是第一行語句卻返回false。認爲這些規則還具備「優先級」,實際上是由於AND運算符最本質邏輯規則:
若是第一個運算數決定告終果,就再也不計算第二個運算數。對於邏輯 AND 運算來講,若是第一個運算數是 false,那麼不管第二個運算數的值是什麼,結果都不可能等於 true。
也就是說AND運算符遇到第一個運算數「false」就中止計算返回false了。而若是第一個運算數是「true」則還要計算第二個運算數,第二個運算數爲「null」,因此根據規則返回null。
注意看規則中說明是「返回」的,說明運算到此運算數則已經返回結果,不會再繼續計算其餘運算數。因此當運算數是null,就已經返回告終果「null」,即便第二個運算數是false也不會參與運算。
(3)OR運算符
OR 運算符與C#中的相同,都由雙豎線(||)表示。若是兩個運算數都是boolean類型,OR運算符的運算邏輯和在幾乎全部的語言中都是相同的,即有一個爲「true」則返回true。
一樣在JavaScript中,OR運算符的運算數不必定是Boolean類型,返回的也不必定是 Boolean 值,可能返回對象。
OR運算符的規則以下:
w 若是一個運算數是對象,另外一個是 Boolean 值,返回該對象。
w 若是兩個運算數都是對象,返回第一個對象。
w 若是某個運算數是 null,返回 null。
w 若是某個運算數是 NaN,返回 NaN。
w 若是某個運算數是爲初始化的undefined,則忽略此操做數。
w 若是某個運算數是未定義的undefined,發生錯誤。
當兩個操做數都是對象時,OR和AND的規則是不一樣的,是否感受記憶困難?其實能夠將對象理解爲「true」,在AND中遇到「true」時須要繼續運算,因此繼續計算到第二個操做數。返回的也是第二個操做數即最後一個參與運算的操做數。在OR運算時遇到「true」即返回,因此返回第一個對象也一樣是最後一個操做數。
OR運算符對於未初始化的undefined類型的對象處理也比較特別,會忽略此操做數,也能夠理解爲看成「false」處理。若是還有第二個操做數則會繼續運算。這一點和AND不一樣,AND運算中若是碰到了未初始化的undefined,則馬上返回undefined。
再次強調,未定義的undefined在全部的邏輯運算符中都會出現錯誤。要儘可能避免使用未定義的對象。
理解了JavaScript的邏輯運算符,就能夠介紹一種在jQuery插件開發等地方常常會使用的JavaScript技巧。假設有下面的方法:
function tsetMethod(param1)
{
alert(param1);
//alert(param2); //輸出 「error」
}
testMethod方法有一個參數param1,由於方法的簽名就帶有param1參數,因此param1和徹底未聲明過的param2是不同的,當調用testMethod方法而且不傳入參數是,會輸出「undefined」。即param1此時在函數體內屬於「未初始化的undefined」對象。
若是但願在testMethod方法中爲param1參數設置默認值「abc」,若是沒有傳入param1參數則使用默認值。有兩種思路,一是能夠經過typeof或者arguments對象判斷是否傳入了param1參數。
經過typeof判斷:
function testMethod1(param1)
{
if (typeof param1 == "undefined")
{
param1 = "abc";
}
alert(param1);
}
或者經過arguments對象判斷:
function testMethod2(param1)
{
if (arguments.length < 1)
{
param1 = "abc";
}
alert(param1);
}
再有能夠藉助OR運算符的規則,讓語句變得更精簡:
function testMethod3(param1)
{
alert(param1 || "abc")
}
由於param1是未初始化的undefined,而字符串「abc」是對象,因此會返回「abc」對象。而若是傳入了param1則會返回param1。這個JavaScript技巧在處理可能爲未初始化的undefined時經常用到。可是在使用可能未定義undefined對象時一樣會發生語法錯誤。有些開發人員不經過參數傳遞數據,而是在方法中經過閉包來使用外層的變量,此時就會有使用未聲明的undefined對象的危險。
本節介紹了JavaScript語言的一些精髓,指出了JavaScript開發人員的一些常見誤區和知識盲點,好比理解ECMAScript,區分JavaScript語言和DOM, 理解DOM元素和HTML元素的區別等。由於篇幅有限不可能對全部的知識進行講解。可是本章的學習將爲後續學習jQuery打下堅實的基礎,而且讓Web開發人員的JavaScript知識體系獲得昇華。