jQuery 1.5筆記(上)
7 |
jQuery 1.5正式版已在2011/1/31釋出,照例我都會寫筆記文強迫自己搞懂改版重點,不過本次適逢過年,把這件事塞在冗長的Todo Queue屁股後,便很鴕鳥地繼續瞎忙工作與生活的大小瑣事。直到前幾天網友ChaN在留言中提了"jQuery 1.5"關鍵字,我才驚覺原來有讀者等文,那就可不好再拖了(謎之聲: 怕什麼? 你不是已經做好"外出取柴,本月休息"的告示圖檔?),趁著熱血乍現,仔細看了jQuery 1.5改版重點。
(由於篇幅過長,拆成上下兩篇)
1.5最重大的改變莫過於重寫了Ajax模式,$.ajax(), $.get(), $.post()都會傳回一個jqXHR物件,把不同瀏覽器中的XMLHttpRequest物件封裝起來,對外提供統一的程式介面,如此當我們要操作XHR時,就可忽略不同瀏覽器的差異性。
重寫Ajax的同時,jQuery 1.5還加入了Deferred物件的概念,其在Mochikit和Dojo等Javascript程式庫中已被運用多時,jQuery則從1.5起也開始支援。在以前,$.ajax/get/post只能指定一個success函數接受傳回結果,一個error函數接受呼叫失敗時傳回的例外。自jQuery 1.5起,我們可以撰寫多個success事件,分別寫不同的邏輯處理Ajax的回傳結果,而更酷的是,我們甚至可以在Ajax已開始非同步呼叫後才加掛success事件,此時jQuery會偵測Ajax呼叫狀態,當結果尚未傳回,則會等到結果傳回時一併呼叫後來加掛的sccess事件;若掛上success時Ajax呼叫結果已經回傳,則sccuess事件會立即被執行。這個新特性可以戲劇化地簡化非同步程式間的協同運作,太抽象? 那用實例來解說好了:
<%@ Page Language="C#" %>
<script runat="server">
void Page_Load(object sender, EventArgs e)
{
if (Request["m"] == "ajax")
{
//接受參數Delay一段時間再回應
if (!string.IsNullOrEmpty(Request["d"]))
System.Threading.Thread.Sleep(
int.Parse(Request["d"]));
//傳回目前時間
Response.ContentType = "text/plain";
Response.Write(DateTime.Now.ToString("HH:mm:ss.fff"));
Response.End();
}
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Ajax Lab1</title>
<script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.5.min.js"
type="text/javascript"></script>
<script type="text/javascript">
$(function () {
//顯示Log, 前方加註記錄時間
function dispLog(msg) {
$("#spnDisp").html(
$("#spnDisp").html() + "<li>" +
new Date().toTimeString().split(' ')[0] +
" => " + msg
);
}
//以Ajax方式取得現在時間
var ajaxCall = $.get("Lab1.aspx?m=ajax" // + "&d=3000",
, { rnd: Math.random() },
function (resp) { //success事件
dispLog("1st Event: " + resp);
});
//記錄開始呼叫時間
dispLog("Begin $.get!");
//等待1000ms再掛上第二個處理回應的事件
setTimeout(function () {
ajaxCall.success(function (resp) {
dispLog("2nd Event: " + resp);
});
}, 1000);
});
</script>
</head>
<body>
<span id="spnDisp"></span>
</body>
</html>
執行以上的程式碼,我們可以得到如下的結果:
- 06:39:56 => Begin $.get!
- 06:39:56 => 1st Event: 06:39:56.513
- 06:39:57 => 2nd Event: 06:39:56.513
利用setTimeout延遲1秒掛上第二個success事件,當時Ajax呼叫已經完成,而第一個success事件也已經執行,因此延遲掛上的事件立即被觸發,並且順利取得剛才傳回的結果。如果我們在ASP.NET呼叫參數加上&d=3000令其強迫延遲3秒再傳回結果,可以預期setTimeout 1秒掛上第二個success事件時,Ajax呼叫還沒完成,因此當3秒後結果傳回時,第一個及第二個success事件會一起被觸發:
- 06:48:48 => Begin $.get!
- 06:48:51 => 1st Event: 06:48:51.634
- 06:48:51 => 2nd Event: 06:48:51.634
以上的實驗,可驗證jQuery Ajax的新特性,允許我們為同一個呼叫加上多個success事件,而且不管掛上事件時Ajax呼叫是否已完成,success都會被執行到。但剛才不是說可以神奇地簡化非同步程式間的協同運作,在哪裡? 換一個複雜一點的例子來說明,假設我們會先後呼叫兩次Ajax,最後加總兩次得到的結果算出答案,程式要怎麼寫?
有好幾種實踐的方法: 1) 先呼叫第1次,在第1次的success事件中發動第2次呼叫,在第2次呼叫的success計算兩次的結果 2) 第1次第2次呼叫的success事件中將結果寫到指定變數,另外用setInterval定期檢查是否兩次的結果都已寫回 3) 寫一個Queue機制,可排入Ajax工作執行,機制可彙整Ajax結果並檢查Ajax工作狀態觸發相關事件。我用一個例子示範1及2的寫法,而3,與Deferred物件精神相似,我直接使用jQuery 1.5的新寫法$.when()來展示:
<%@ Page Language="C#" %>
<script runat="server">
static Random rnd = new Random();
void Page_Load(object sender, EventArgs e)
{
if (Request["m"] == "ajax")
{
//Delay 1到3秒,傳回1-100亂數
Response.ContentType = "text/plain";
System.Threading.Thread.Sleep(rnd.Next(1000, 3000));
Response.Write(rnd.Next(1, 100));
Response.End();
}
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Ajax Lab1</title>
<script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.5.min.js"
type="text/javascript"></script>
<script type="text/javascript">
$(function () {
//顯示Log, 前方加註記錄時間
function dispLog(msg) {
$("#spnDisp").html(
$("#spnDisp").html() + "<li>" +
new Date().toTimeString().split(' ')[0] +
" => " + msg
);
}
//設定不要Cache
$.ajaxSetup({ cache: false });
var url = "Lab2.aspx?m=ajax";
test1();
function test1() {
//方法1
dispLog("Test1");
//兩個變數用來保存兩次呼叫的結果
var v1 = 0, v2 = 0;
$.get(url, {},
function (resp) {
v1 = parseInt(resp);
dispLog("v1=" + v1);
$.get(url, {}, function (resp2) {
v2 = parseInt(resp2);
dispLog("v2=" + v2);
//最後結果
dispLog("v1+v2=" + (v1 + v2));
});
});
}
function test2() {
//方法2
dispLog("Test2");
var tv1 = null, tv2 = null;
$.get(url, {}, function (resp) {
tv1 = parseInt(resp);
dispLog("tv1=" + tv1);
});
$.get(url, {}, function (resp) {
tv2 = parseInt(resp);
dispLog("tv2=" + tv2);
});
//利用輪詢檢查兩次呼叫是否都已完成
var hnd = setInterval(function () {
if (tv1 != null && tv2 != null) {
dispLog("tv1+tv2=" + (tv1 + tv2));
clearInterval(hnd);
}
}, 200);
}
function test3() {
//Deferred
dispLog("Test3");
var dv1 = null, dv2 = null;
var ajax1 = $.get(url, {}, function (resp) {
dv1 = parseInt(resp);
dispLog("dv1=" + dv1);
});
var ajax2 = $.get(url, {}, function (resp) {
dv2 = parseInt(resp);
dispLog("dv2=" + dv2);
});
$.when(ajax1, ajax2) //用when排入ajax1, ajax2所傳回的物件
.done(function () { //ajax1或ajax2都成功能會呼叫done
dispLog("dv1+dv2=" + (dv1 + dv2));
})
.fail(function () { //ajax1或ajax2任一者失敗時執行fail
}); //或用.then(doneFunc, failFunc)一次設定兩種結果函數
}
});
</script>
</head>
<body><span id="spnDisp"></span></body>
</html>
經測試,三者都可以得到正確結果:
- 07:35:38 => Test1
- 07:35:41 => v1=61
- 07:35:43 => v2=38
- 07:35:43 => v1+v2=99
- 07:43:47 => Test2
- 07:43:50 => tv2=2
- 07:43:50 => tv1=20
- 07:43:50 => tv1+tv2=22
- 07:49:23 => Test3
- 07:49:25 => dv1=76
- 07:49:25 => dv2=63
- 07:49:25 => dv1+dv2=139
比較三種做法: 在方法1,下一次的呼叫方法必須藏進前一次呼叫的success裡,若串上四層,光程式碼內縮排版就包準讓你頭暈,更不用說會變成單執行緒的缺點。方法2用了輪詢(Polling),嚴格來說不是有效率的做法,在江湖上常被視為雞鳴狗盜之技(但坦白說我還挺愛用的,不用花太多腦筋,簡單有效立竿見影),而且Closure加clearInterval的玩法挺Tricky。至於方法3就是jQuery 1.5透過Deferred物件實現非同步程序同步的新做法,與前兩種做法相比直覺簡潔很多,我只有一句話: 我愛死它了!
Deferred物件的觀念不只應用在Ajax呼叫上,我們還可以用來簡化複雜的非同步協同作業,有興趣的朋友可以參考jQuery API文件及Eric Hynds的介紹文。
Comments
# by 鐵衛
感謝黑大的犧牲奉獻,容小弟在這邊插個頭香膜拜一下 Orz
# by ChaN
感謝黑暗大滿足粉絲的需求 囧
# by jocosn
黑大,我一直覺的你的部落格技術文章寫的很好。 唯一美中不足的是,右邊 sidebar 寬度好長,左邊程式碼時常會被擠壓。美中不足。
# by Jeffrey
to jacosn, 目前部落格是採用Community Server預設版型之一為藍本調整出來,分配上是左600px, 右200px。當時600px的寬度還有考量到若讀者使用Google Reader之類訂閱工具直接開啟時,排除左方清單佔用的空間,閱讀視窗也差不多剩下這個寬度,維持在600px以下可以避免左右捲動。不過十分謝謝你的意見,我會納入未來改版的考量。
# by 低溫烘培
黑大,關於jocosn的問題,我覺得你可以直接用JQuery寫個小片斷的按鈕程式,按了之後,右邊的sidebar自動縮起來就好了。不過我不確定你左邊程式碼部分是不是會自動擴展就是了。
# by 小和
感謝黑大的分享~~ 請問我是否可以將您範例放在我網站上,我會註明程式是來自黑大網站~~
# by Jeffrey
to 小和,本部落格的文章都歡迎引用,註明出處即可。