毫無疑問,到目前為止,JavaScript應(yīng)用最為廣泛,也最為成功的領(lǐng)域就是客戶端,或者稱為瀏覽器上的JavaScript。JavaScript為頁面開發(fā)注入了活力,如與服務(wù)器交互形成的局部刷新,鼠標(biāo)事件的響應(yīng),動態(tài)的頁面風(fēng)格變換等等,都直接依靠與JavaScript的支持。
我們在基礎(chǔ)部分提到過,JavaScript代碼都有一個執(zhí)行環(huán)境,所有的JavaScript代碼均被包含在一個全局對象中,比如在所有函數(shù)之外聲明:
var x = 3;
var str = "global";
這兩個變量聲明語句事實(shí)上是為全局對象添加了兩個屬性x和str??梢詫⑷謱ο笙胂蟪梢粋€大的匿名自執(zhí)行函數(shù):
<span style="font-family: 'Courier New'; font-size: x-small;">(function(){
var x = 3;
var str = "global";
//...
})();</span>
在瀏覽器端,這個全局的對象稱為window,window是所有JavaScript對象的根,我們可以通過window對象的屬性document來訪問頁面本身,也可以調(diào)用window的一些方法來與用戶交互,比如:
window.alert("This is a message");
alert("This is a message");
這兩個語句事實(shí)上的效果是相同的,在引用全局對象window的方法時,我們可以忽略前綴,只使用方法名本身。
window對象是對瀏覽器當(dāng)前窗口的引用,因?yàn)闉g覽器會將window與自身繪制出來的窗口綁定起來,我們對window的操作事實(shí)上會映射到瀏覽器窗口上。正是瀏覽器本身提供了這種腳本化的能力,我們才有機(jī)會通過JavaScript代碼來完成諸如改變頁面標(biāo)題,彈出警告框,修改頁面內(nèi)容(通過對window.document的修改)等操作。
DOM即文檔對象模型,它是一個平臺,提供語言無關(guān)的API,允許程序訪問并更改文檔的內(nèi)容,結(jié)構(gòu)以及樣式。HTML文檔是一個樹形的結(jié)構(gòu),與瀏覽器中的頁面中的結(jié)構(gòu)一一對應(yīng)。
http://wiki.jikexueyuan.com/project/javascript-core/images/jsc.gif" alt="" />
圖 W3C站點(diǎn)中的一個HTML結(jié)構(gòu)示例
JavaScript通過修改/遍歷DOM,即可映射到對WEB頁面的操作上,比如一個簡單的頁面如下:
<html>
<head>
</head>
<body>
<div id="con">
</div>
</body>
</html>
通過JavaScript操作DOM:
var con = document.getElementById("con");
即可對應(yīng)到WEB頁面中的id為”con”的div標(biāo)簽,可以為該標(biāo)簽設(shè)置背景色,或者綁定click事件的處理函數(shù)等等。JavaScript可以通過一些瀏覽器內(nèi)置的方法來對DOM進(jìn)行遍歷,增加,刪除DOM的子節(jié)點(diǎn),訪問DOM上的Form,F(xiàn)rame,Image等節(jié)點(diǎn),修改他們的CSS樣式,注冊/注銷事件處理函數(shù),從而使頁面“活動”起來。
由于客戶端JavaScript的開發(fā)屬于用戶界面開發(fā)的范疇,因此使用事件驅(qū)動模型就顯得非常自然了,事件驅(qū)動的特點(diǎn)在于:代碼塊的運(yùn)行與否與程序流程無關(guān)。而傳統(tǒng)的流式代碼的特點(diǎn)是,從函數(shù)的入口進(jìn)入,依次調(diào)用各個子模塊的處理函數(shù),最后退出。這種編程模式主要適用于與UI關(guān)系不大的場合,很少有異步的過程,這些特點(diǎn)可能要追溯到計算機(jī)程序的最初模型:批處理。
而 事件驅(qū)動模型主要是面向用戶的,你在實(shí)現(xiàn)無法知道用戶會如何使用你的程序,因此就只能通過回調(diào),事件監(jiān)聽等方式,當(dāng)用戶做出某個動作時才會觸發(fā)之前已經(jīng)注 冊好的監(jiān)聽器,從而執(zhí)行相關(guān)代碼,而不是順序的執(zhí)行。甚至在一次運(yùn)行中,部分代碼始終沒有被觸發(fā),也就根本不會被執(zhí)行到。
比如在頁面中,我們?yōu)榘粹o的點(diǎn)擊事件注冊了事件監(jiān)聽器,當(dāng)點(diǎn)擊的時候彈出一個對話框,但是用戶打開頁面后,瀏覽完內(nèi)容后就直接關(guān)閉了,沒有點(diǎn)擊按鈕,那么相關(guān)的代碼就沒有被觸發(fā),我們來看一個簡單的示例:
我們有一個頁面,內(nèi)容如下:
<html>
<head>
<style>
body{
margin: 20px;
font-family : "Verdana";
font-size : normal;
background-color : #eee;
}
</style>
</head>
<body onload="init()">
<p>
<label for="toclick">Please click the button:</label>
<input id="toclick" type="button" value="Click me" />
</p>
</body>
</html>
http://wiki.jikexueyuan.com/project/javascript-core/images/jsc1.png" alt="" />
圖 頁面click-me的展示
可以看到body標(biāo)簽的onload屬性被賦予一個值”init()”,這是一個對JavaScript代碼的調(diào)用,時機(jī)發(fā)生在當(dāng)頁面加載完成之后,也就是瀏覽器已經(jīng)創(chuàng)建了所有body中的DOM對象之后。
我們來看看這個init函數(shù)的內(nèi)容:
<script type="text/javascript" language="javascript">
function init(){
var button = document.getElementById('toclick');
button.onclick = function(){
alert('button is clicked');
}
}
</script>
這段代碼先從document中獲取id為”toclick”的元素(也就是我們剛才在html中聲明的button),然后為其click事件綁定一個函數(shù),當(dāng)click事件被觸發(fā)時,彈出一個警告框。
http://wiki.jikexueyuan.com/project/javascript-core/images/jsc2.png" alt="" />
Ajax本身被作為前端技術(shù),提出的時間是比較早的,但是由于種種原因,沒有引起人們的普遍關(guān)注,而當(dāng)Google的很多產(chǎn)品如Google Map,GMail等橫空出世的之后,Ajax才被越來越多的公司所接受并引入到自己的產(chǎn)品中。現(xiàn)在幾乎所有的網(wǎng)站都有不同層次的Ajax交互,純靜態(tài)的頁面已經(jīng)非常少了。
簡而言之,Ajax表示異步JavaScript與XML,事實(shí)上,Ajax與XML幾乎沒有任何關(guān)系,因?yàn)槭钱惒浇换?,所以用戶的頁面不用刷新,在同一個頁面中,客戶端請求服務(wù)數(shù)據(jù),當(dāng)服務(wù)數(shù)據(jù)返回時,通過JavaScript將數(shù)據(jù)片段填充到頁面的某個部分,即實(shí)現(xiàn)了局部刷新,這個過程對用戶來說實(shí)際上是透明的。
Ajax對服務(wù)器端沒有限制,一個Ajax請求和普通的請求一樣,發(fā)送數(shù)據(jù)(GET/POST)到一個服務(wù)端URL上。服務(wù)端的實(shí)現(xiàn)可以是php,可以是ASP或者JSP等。以J2EE為例,數(shù)據(jù)發(fā)送到servlet,servlet對請求進(jìn)行處理,最后向輸出流寫入數(shù)據(jù),瀏覽器得到這些數(shù)據(jù)之后,會按照Ajax調(diào)用時的注冊情況來回調(diào)JavaScript函數(shù),JavaScript在操作DOM上具有天生的優(yōu)勢,因此可以很方便的在無刷新的情況下更新頁面的部分或者全部。
下面我們來看一個具體的實(shí)例,通過異步的向后端的php腳本發(fā)送請求,頁面的內(nèi)容得到了局部更新,而無需重刷整個頁面。首先編寫一個簡單的php腳本:
<?php
$type = $_REQUEST['type'];
$string = $_REQUEST['string'];
if($type == 0){
echo strtoupper($string);
}else{
echo strtolower($string);
}
?>
php的邏輯很簡單,請求中包含兩個參數(shù),type和string,如果type為0,則將第二個參數(shù)string轉(zhuǎn)換為大寫返回,否則轉(zhuǎn)換為小寫返回。
XMLHttpRequest對象是進(jìn)行Ajax的核心,所有的主流瀏覽器都已各自不同的方式進(jìn)行了實(shí)現(xiàn),如果不使用客戶端JavaScript框架,那么使用起來會有所差異:
function initxhr(){
if(window.XMLHttpRequest){
xhr = new XMLHttpRequest();
}else if(window.ActionXObject){
xhr = new ActiveXObject("Msxml2.XMLHTTP");
}else{
throw new Error("xhr is not supported");
}
}
創(chuàng)建了XMLHttpRequest對象之后,我們即可使用它來進(jìn)行與服務(wù)端的異步通信:
function doajax(url, panelId){
if(xhr == null){
initxhr();
}
if(xhr != null){
xhr.open("GET", url, true);
xhr.onreadystatechange = updatePanel(panelId);
xhr.send(null);
}else{
throw new Error("xhr is not inited");
}
}
通過設(shè)置XMLHttpRequest對象的onreadystatechange屬性,當(dāng)服務(wù)端產(chǎn)生響應(yīng)時,瀏覽器會回調(diào)此處注冊的函數(shù):
function updatePanel(panelId){
return function(){
if(xhr.readyState == 4){
var response = xhr.responseText;
alert(response);
document.getElementById(panelId).innerHTML = response;
}
}
}
運(yùn)行結(jié)果如下:
http://wiki.jikexueyuan.com/project/javascript-core/images/jsc3.png" alt="" />
在實(shí)際的開發(fā)環(huán)境中,不可能做到代碼沒有bug,變量的拼寫錯誤,使用了未經(jīng)初始化的變量,死循環(huán)等等,這些都是比較容易解決的錯誤,但是如果有隱藏的比較深的bug,時隱時現(xiàn),我們就需要一個調(diào)試環(huán)境了。腳本語言的調(diào)試尤其艱難,因?yàn)橐磺卸际沁\(yùn)行時決定的,因此不可能預(yù)先知道代碼有錯誤(不包括顯式的詞法錯誤)。
我們這一節(jié)中介紹集中瀏覽器中的調(diào)試JavaScript的方式。
FireFox以速度,可擴(kuò)展性,標(biāo)準(zhǔn)性深受廣大開發(fā)人員的喜愛。FireFox具有強(qiáng)大的插件機(jī)制,用戶可以自己為FireFox增添新的功能(很多插件只需要有JavaScript/CSS/XUL經(jīng)驗(yàn)即可),而WEB開發(fā)人員最喜愛的插件之一即為:FireBug。FireBug是一個FireFox的插件,使用它,使前端開發(fā)人員丟棄了老舊的alert,F(xiàn)ireBug提供一個控制臺,開發(fā)人員可以直接像在windows的控制臺那樣,通過簡單的命令來查看變量的值,狀態(tài)等。
作為一個調(diào)試工具,F(xiàn)ireBug提供如其他IDE中那樣的基本功能,斷點(diǎn),步進(jìn),watch等等功能,下面我們來詳細(xì)介紹FireBug:
http://wiki.jikexueyuan.com/project/javascript-core/images/jsc4.png" alt="" />
通過在提示符”>>>”之后鍵入JavaScript代碼即可執(zhí)行這些代碼,由圖可見FireBug有6個標(biāo)簽:控制臺,HTML查看器,CSS查看器,腳本查看器,DOM查看器,以及網(wǎng)絡(luò)性能監(jiān)控。
http://wiki.jikexueyuan.com/project/javascript-core/images/jsc5.png" alt="" />
在FireBug處于“查看”狀態(tài)時,通過鼠標(biāo)在頁面上移動,頁面的一部分區(qū)域會高亮,點(diǎn)擊此高亮區(qū)域即進(jìn)入查看狀態(tài),注意此時FireBug的HTML查看窗口中的內(nèi)容:它會高亮顯示當(dāng)前選中區(qū)域的HTML代碼,而且你可以動態(tài)的編輯這些標(biāo)簽的屬性和樣式,對于頁面大小,風(fēng)格的微調(diào)十分有用。
http://wiki.jikexueyuan.com/project/javascript-core/images/jsc6.png" alt="" />
通過DOM查看器,我們可以清晰的看到頁面中所有JavaScirpt對象,而且是以樹的形式,可以逐層點(diǎn)開,查看詳情,這對于調(diào)試頁面非常有用。
http://wiki.jikexueyuan.com/project/javascript-core/images/jsc7.png" alt="" />
通過使用FireBug,我們就可以丟棄alert方式的調(diào)試了,F(xiàn)ireBug的調(diào)試與其他的IDE的使用非常類似,通過在代碼左側(cè)的行號旁邊單擊打斷點(diǎn),然后刷新頁面進(jìn)入調(diào)試環(huán)境,可以單步執(zhí)行,直接返回等。右側(cè)的面板上包含watch,棧情況,以及關(guān)于所有斷點(diǎn)的信息,調(diào)試起來非常方便。
FireBug的控制臺中常用的一些命令:
記錄日志,一般用于調(diào)試時將JavaScript字符串打印出來
console.log(object[, object, ...])
打印消息,用于調(diào)試
console.info(obj)
用于打印一個對象,將JavaScript對象的各個屬性樹以直觀的形式展現(xiàn)。
console.dir(obj)
console.trace()
Chrome是Google的瀏覽器,渲染引擎為蘋果公司(Apple)的開源項(xiàng)目WebKit,而JavaScript引擎為V8,根據(jù)作者經(jīng)驗(yàn),Chrome為當(dāng)前瀏覽器界綜合性能最好的一款瀏覽器,無論是速度,資源的占用,響應(yīng)時間,以及界面,可擴(kuò)展性等方面,遠(yuǎn)遠(yuǎn)的超過了同儕。
Chrome一開始就附有開發(fā)人員工具,前端開發(fā)人員可以使用這個工具進(jìn)行類似與FireBug那樣對代碼進(jìn)行調(diào)試,同時可以動態(tài)增刪HTML屬性,查看頁面響應(yīng)時間,以便找出系統(tǒng)的瓶頸等。
像FireBug一樣,Chrome的內(nèi)置開發(fā)人員工具也可以查看JavaScript的運(yùn)行時結(jié)構(gòu),我們可以通過下面的一些圖例來看Chrome的調(diào)試工具的用法:
在chrome中,CTRL-SHIFT-I啟動開發(fā)人員工具:
http://wiki.jikexueyuan.com/project/javascript-core/images/jsc8.png" alt="" />
在console標(biāo)簽中,可以進(jìn)行一些腳本的測試:
http://wiki.jikexueyuan.com/project/javascript-core/images/jsc9.png" alt="" />
在實(shí)際的項(xiàng)目中,console系列的方法非常有用,特別是web容器如tomcat等返回的是復(fù)雜的JavaScript對象時,console.dir(object)可以直觀的讓開發(fā)人員看到對象的結(jié)構(gòu),非常便于調(diào)試。
MVC模型是在實(shí)際應(yīng)用中使用的較多的一種模式,它在很大程度上降低了整個應(yīng)用開發(fā)的復(fù)雜度。在J2EE應(yīng)用中,使用了MVC的框架不計其數(shù),比如structs,JSF等等。當(dāng)然MVC作為一種應(yīng)用程序的模型并不限制其使用的場景,比較著名的Swing工具包也是建立在MVC模型上的。
在MVC模型中,應(yīng)用被分為三個功能塊,M表示模型(Model),通常為后臺的數(shù)據(jù);V表示視圖(View),表示數(shù)據(jù)的展現(xiàn),如Web頁面或者2D庫渲染出來的其他UI組件,而C表示控制器(Controller),負(fù)責(zé)邏輯部分的控制,協(xié)調(diào)模型和視圖的關(guān)系。使用這個模型,可以降低個層次之間的耦合度,使得軟件可以較大程度上得到重用。
一般而言,MVC是構(gòu)建在整個系統(tǒng)中的,比如應(yīng)用的數(shù)據(jù)來自于遠(yuǎn)程數(shù)據(jù)庫或本地的文件系統(tǒng),視圖則在前端,直接展示給用戶,控制器部分則運(yùn)行在容器端。我們這個小節(jié)要討論的并不是這個結(jié)構(gòu),而是純粹建立在前端的MVC。
http://wiki.jikexueyuan.com/project/javascript-core/images/jsmvc.png" alt="" />
既然MVC模型的目的是降低層次間的耦合,降低開發(fā)的復(fù)雜度,使得軟件盡量的到重用,我們不妨建立這樣一套規(guī)則:
<!--[if !supportLists]-->l <!--[endif]-->用HTML表示模型
<!--[if !supportLists]-->l <!--[endif]-->用CSS來負(fù)責(zé)渲染視圖
<!--[if !supportLists]-->l <!--[endif]-->用JavaScript負(fù)責(zé)控制前兩者
在HTML中,只是將文檔的結(jié)構(gòu)和內(nèi)容組織起來,通過CSS進(jìn)行渲染,布局等工作,而與用戶交互的部分則由JavaScript來控制,我們來看這樣一個例子:我們有一個頁面,其中有一個panel,當(dāng)點(diǎn)擊這個panel時其背景色會發(fā)生變化。
這個HTML文件看起來應(yīng)該是這樣的:
<html>
<head>
<script src="jquery-1.3.2.js" type="text/javascript" ></script>
<script src="controller.js" type="text/javascript"></script>
<link rel="stylesheet" href="style.css" type="text/css" />
</head>
<body>
<div class="contentGray" id="content">
This is the content of a panel
</div>
</body>
</html>
它引入了兩個腳本文件(控制器)和一個樣式表文件(視圖風(fēng)格),而HTML本身只不過定義了div的結(jié)構(gòu)和其位于body中的層次關(guān)系。
我們的視圖渲染部分:樣式表的內(nèi)容是這樣的:
.contentGray{
background : #ccc;
color : blue;
border : 1px solid black;
font: 13px 'Courier New';
width : 300px;
height : 30px;
padding-top : 10px;
padding-left : 10px;
}
.contentDark{
background : #666;
color : white;
border : 1px solid black;
font : 13px 'Courier New';
width : 300px;
height : 30px;
padding-top : 10px;
padding-left : 10px;
}
CSS文件定義了類(contentGray)的樣式及另外一個類(contentDark)的樣式,這樣HTML文件就可以看起來比較漂亮:
http://wiki.jikexueyuan.com/project/javascript-core/images/jsc10.png" alt="" />
我們的需求是,當(dāng)我們點(diǎn)擊這個panel的時候,它的背景色加深,文字變成白色,當(dāng)再次點(diǎn)擊的時候又恢復(fù)之前的顏色和背景色。這種“動態(tài)”的就交給JavaScript來處理了:
$(document).ready(function(){
$("div#content").click(function(){
$(this).toggleClass("contentDark");
})
});
使用jQuery可以為我們帶來很多便利,我們選擇id為content的div,然后注冊事件處理函數(shù),當(dāng)click事件發(fā)生的時候,我們就切換CSS類contentDark:
http://wiki.jikexueyuan.com/project/javascript-core/images/jsc11.png" alt="" />
當(dāng)然,這個只是一個簡單的例子,如果你的應(yīng)用所涉及的頁面比較多,而且控制器部分(JavaScript腳本)的工作量比較大,那么你會發(fā)現(xiàn),客戶端的MVC模型對你有很大的幫助,不但是界面的統(tǒng)一風(fēng)格,而且JavaScript代碼會更加模塊化,從而可能一定程度上提高應(yīng)用程序的效率,同時降低維護(hù)的難度。
隨著富客戶端的流行,基于WEB的應(yīng)用越來越多,人們在開發(fā)過程中不再滿足于DOM提供的簡單API,特別是DOM對頁面的操作比較繁瑣,而且容易出錯。當(dāng)頁面越來越華麗,頁面UI越來越復(fù)雜(事件處理,特效處理)的時候,就有大量第三方JavaScript框架被開發(fā)出來了,比如較早的prototype,dojo,以及yahoo的YUI,后來的jQuery,以及jQuery的UI插件jQuery-UI,最早基于YUI而后來又進(jìn)行了重構(gòu)的ExtJs,號稱純OO的Mootools等等。
這些JavaScript框架的開發(fā),大大的簡化了頁面的開發(fā)速度,也提高了開發(fā)效率,同時比較注重用戶體驗(yàn),這里列舉出的框架幾乎都是完全免費(fèi),所以應(yīng)用十分廣泛。我們在隨后的兩章中將列舉兩個最流行的框架做一些介紹,以期讀者可以有一些感性的認(rèn)識,關(guān)于jQuery和ExtJS的深入的研究已經(jīng)大大的超出了本書的范圍(事實(shí)上每一個框架都足以寫一本書),有興趣的讀者可以參考相關(guān)的書籍。