用ASP.NET2.0實現AJAX風格的Web開發
提要 在過去的幾個月中,基于AJAX技術開發高度交互的Web應用程序的設計模式迅速流行開來。現在,具有高度可配置性的Web應用程序,例如Google Maps和A9,都在綜合利用這些技術來創造豐富的客戶端用戶體驗。其實,結合AJAX技術進行Web開發并非最近的研究成果,只不過這些技術一直以來不斷得到持續更新和改進。
本文中我有三個目的。首先,我想提供一個AJAX風格應用程序的高級概述。其次,我想詳細地描述ASP.NET 2.0的異步回調機制。最后,我想對構建AJAX風格應用程序的工具和框架的未來改進作一下展望。
歸納來看,AJAX風格的Web應用程序展示了下列特征:
· 到Web服務器的異步請求-在用戶等待來自于Web服務器的響應時,瀏覽器用戶接口不會被堵塞,而是可以繼續響應用戶的交互。
· 高度依賴于用JavaScript編寫的基于瀏覽器的邏輯-W3C DOM的最新改進和標準化為實現動態的客戶端UI更新提供了支持。
· 在瀏覽器和Web服務器之間的基于XML數據的交換-XMLHttp對象使得與Web服務器進行通訊而不需要重載頁面成為可能。
一個AJAX應用程序和傳統型Web應用程序之間的最大差別是,每次用戶交互不會導致每一個HTTP請求都被發送到Web服務器;而是,用JavaScript實現的基于瀏覽器的邏輯掌握著控制權,之后再由該控制決定是局部處理請求還是向服務器作異步調用。一旦到服務器的異步調用結束,客戶端邏輯立即適當更新UI的相關部分。這種方式具有下列優點:
· 用戶體驗更為豐富。例如,當一個Google地圖用戶沿一個方向拖動地圖時,系統就會在后臺向服務器發出一個異步請求,結果是他能夠在超出屏幕邊界后繼續拖動。這樣以來,當用戶進一步拖動地圖時,新的圖像已經可用了。這導致一種響應更快的感覺。
· 既然跨越基于XMLHttp的到服務器的調用狀態并沒有丟失,那么,AJAX應用程序就可以避免每次都重新生成UI界面。
· 更多的邏輯位于瀏覽器端,從而減少了到Web服務器的來回請求的數量,進而全面改進系統的潛力。
盡管存在這么多的優點,然而AJAX風格的應用程序還是存在一些不足之處。例如,AJAX風格應用程序的開發是比較困難的,因為缺乏相應的框架(一組類似于Windows MFC工具包的UI類)和IDE(調試,可視化設計,等等)支持。另外,基于AJAX進行開發要求一個人必須至少掌握兩種語言(DHTML和JavaScript)。而且,AJAX風格應用程序的編碼需要更長的時間,因為它需要另外的測試以使其支持多瀏覽器版本和類型。最后,由于基于JavaScript的源碼為終端用戶可存取,所以開發過程中的安全分析也變得非常重要。
幸好,例如Atlas,AJAX.NET和Google Maps API等工具的出現為將來構建AJAX風格的應用程序提供了更好的支持。接下來,我們將討論一下,對于構建AJAX風格應用程序的支持技術的發展歷程以及我們能夠從最新發布的工具集Atlas得到怎樣的期望。
讓我們首先討論XMLHttp對象。這個對象最初為微軟所引入,以后在其它平臺(包括Mozilla和蘋果公司的Safari瀏覽器)上也得到實現。XMLHttp支持到Web服務器的異步請求,這樣可以允許客戶端基于JavaScript邏輯調用Web服務器而不需要重載整個頁面。
換句話說,在后臺與Web服務器的交互而不引起整個頁面重載是完全有可能的。
至于XMLHttp對象的使用則相當直接。為簡單起見,讓我們僅考慮IE特定的語法。其實,XMLHttp在其它瀏覽器上的實現語法與這里的討論也很類似。
在上面的代碼片斷中,第一步實現實例化Microsoft.XMLHttp類。第二步設置我們剛剛創建的XMLHttp實例的屬性,其中包括當XMLHttp請求完成時將得到控制的回調函數的地址。因為我們在向服務器作異步調用(通過把open方法的第三個參數設置為true來實現),所以我們需要回調函數的地址。在回調函數實現過程中,我們作額外的檢查以確保完成請求。
你從上面的示例代碼中可以看出,以獨立方式使用XMLHttp對象是相當簡單的。然而,把XMLHttp集成到HttpPage生命周期的其它部分中是比較困難的-例如,如何確保服務器端的方法調用能夠存取頁面中其它控件的狀態呢?為了正確初始化這些控件的狀態,服務器端的回調處理需要經歷一個與回調過程類似的HttpPage生命周期。直接使用XMLHttp對象的其它挑戰是,作為開發者,我們需要考慮不同的瀏覽器類型。幸好,ASP.NET 2.0提供了一個可重用的模式-它能夠使得存取回調功能非常容易。注意,隨同ASP.NET 2.0一同發行了若干控件,包括GridView,TreeView等,都綜合利用了回調機制。
圖1描述了ASP.NET 2.0中的回調實現原理。讓我們先看一下服務器端實現原理。首先,在服務器端要定義一個新的接口IcallBackEventHandler。任何ASPX頁面(或打算支持客戶端回調的控件)都需要實現這個ICallBackEventHandler接口。ICallBackEventHandler接口定義了一個稱為RaiseCallbackEvent的方法。這個方法使用一個字符串類型的參數并且返回一個字符串。
在客戶端,為了初始化回調功能,需要調用一個特殊的JavaScript函數。你可以通過調用ClientScriptManager.GetCallbackEventReference來獲得一個到這個特殊的JavaScript函數的引用。到GetCallbackEventReference的調用將產生一個回調引用。當調用此回調函數時,你只需要傳遞一個字符串類型的參數。這是與服務器端的RaiseCallbackEvent簽名一致的。這就是你在客戶端建立回調機制所需做的一切。其它的把客戶端回調函數鉤(hook up)到服務器端的IcallBackEventHandler接口的RaiseCallbackEvent方法的實現則是由框架來完成的。前面提到的初始化回調機制的特殊JavaScript函數使用了另外兩個參數(__CALLBACKPARAM和__CALLBACKID)作為回饋數據,它們分別代表傳遞到調用者的字符串參數和控件的ID。在服務器端,ASP.NET檢測其它兩個參數的存在并且會把請求路由到適當的控件,這將導致調用目標控件上的RaiseCallbackEvent方法。為了解決前面提到的頁面上的控件的初始化問題,ASP.NET運行時刻在服務一次回調時提供了一個簡化版本的HttpPage生命周期。這一周期包括瀏覽頁面初始化的某個具體階段,觀察狀態加載,頁面加載和回調函數事件處理等。一旦回調函數事件被控件所處理,HttpPage生命周期的其它階段就會被跳過。
為了幫助更好地理解ASP.NET 2.0的回調機制,發行包中包括了一個簡單的進度條控件,它依靠回調來決定服務器確定的一項任務的狀態。下面的列表1顯示了該ProgressBar控件的代碼。為了支持客戶端回調函數,這個控件實現了ICallbackEventHandler接口。為了演示之目的,RaiseCallbackEvent方法實現簡單地查找存儲在會話中的一個計數器,每次給計數器加1,并且把新值返回到客戶端。最后,列表2顯示了負責初始化該回調函數的JavaScript代碼。它使用了this.Page.ClientScript.GetCallbackEventReference來獲得一個到需要初始化回調的函數的安全引用。
列表1:ProgressBar.cs
列表2:ProgressBar.js
通過使用在ASP.NET 2.0提供的客戶端回調函數,實現進度條控件是比較直接的,因為在控件和客戶端之間傳遞的數據僅是一個簡單的字符串。然而,一旦我們把其它數據類型也添加到其中,我們就遇到在JavaScript和.NET類型系統之間不匹配的問題。遺憾的是,ASP.NET 2.0中的回調函數實現對此并無多大幫助。任何想使用多種數據類型(簡單類型和復雜類型)的應用程序,都要實現一種自己的定制模式。
幸好,這種限制能夠通過使用一個AJAX.NET開源庫來加以克服,AJAX.NET實現了一種基于代理的方式來調用服務器端函數。AJAX.NET定義了一種稱為AJAXMethod的定制屬性。當一個服務器端方法用AJAXMethod加以修飾時,一個基于JavaScript的客戶端代理將被HttpHandler(它是AJAX.NET庫的一部分)自動生成。不同于ASP.NET 2.0,它支持單個參數的字符串類型以便用于回調實現。AJAX.NET支持整數,字符串,雙精度數,DateTime,DataSet等多種類型。
Bertrand Le Roy建議使用AJAX.NET來處理JavaScript和.NET類型系統之間的差別。他創建了一種稱為EcmaScriptObject的服務器端控件-它基于.NET技術重新創建了JavaScript類型系統。其想法是,用.NET重新生成一種客戶端對象圖。當轉換發生在服務器端時,這種方法顯得更有意義。
即使我們有了一種類型安全的方法來調用回調函數,但是,我們還面臨其它的挑戰。JavaScript擔當起了把AJAX應用程序的各個部分組合到一起的"膠水"的作用。當然,相應地,對JavaScript的依賴性也進一步增加。遺憾的是,盡管JavaScript是一種強有力且通用的語言,但是它并沒有實現面向對象的原則。這意味著,要實現代碼重用可能更為困難。當然,可以使用一些技巧來使JavaScript看上去更象傳統的面向對象語言。不過即使如此,要實現托管語言中的例如事件和代理等特征仍然相當困難。
其它困難還包括:缺乏一個可重用框架來進一步提高JavaScript的開發效率。如果有一種基于JavaScript的能夠隱蔽不同執行環境區別的UI框架或許更好些。另外,如果能夠創建一組類,它們可以用一種安全的方式(相對于手工編碼SOAP包并使用XMLHttp來傳遞它們)來調用Web服務,也會相當不錯。
最近來自微軟的Atlas工程許諾要重點解決這類問題。這是一種極大程度地簡化AJAX風格開發的偉大嘗試。Atlas提供了一種新的JavaScript框架(注意,下面是基于微軟的一次初步宣布,以后有可能發生改變)-UI開發工具包。這其中包括:支持諸如拖放和數據綁定等特征的常用控件;調用Web服務的SOAP棧;隱蔽瀏覽器差別的瀏覽器兼容層;包括例如本地緩沖等內容的客戶端構建模塊。另外,ASP.NET團隊還計劃為ASP.NET開發其它構建模塊,例如配置管理,成員管理等,以便把它們用作Web服務端點,從而實現可以直接從JavaScript中對Web服務進行存取-例如可以容易地從客戶端存取個人信息。最后,Atlas工程還計劃擴展JavaScript語法以便包括接口、生命周期管理和multicast事件。
據說,接下來的幾個月將是令AJAX開發者激動的日子。因此,我非常希望本文能夠激起您對AJAX的興趣,并在你以后構建下一代Web應用程序時優先考慮使用這一技術
本文中我有三個目的。首先,我想提供一個AJAX風格應用程序的高級概述。其次,我想詳細地描述ASP.NET 2.0的異步回調機制。最后,我想對構建AJAX風格應用程序的工具和框架的未來改進作一下展望。
歸納來看,AJAX風格的Web應用程序展示了下列特征:
· 到Web服務器的異步請求-在用戶等待來自于Web服務器的響應時,瀏覽器用戶接口不會被堵塞,而是可以繼續響應用戶的交互。
· 高度依賴于用JavaScript編寫的基于瀏覽器的邏輯-W3C DOM的最新改進和標準化為實現動態的客戶端UI更新提供了支持。
· 在瀏覽器和Web服務器之間的基于XML數據的交換-XMLHttp對象使得與Web服務器進行通訊而不需要重載頁面成為可能。
一個AJAX應用程序和傳統型Web應用程序之間的最大差別是,每次用戶交互不會導致每一個HTTP請求都被發送到Web服務器;而是,用JavaScript實現的基于瀏覽器的邏輯掌握著控制權,之后再由該控制決定是局部處理請求還是向服務器作異步調用。一旦到服務器的異步調用結束,客戶端邏輯立即適當更新UI的相關部分。這種方式具有下列優點:
· 用戶體驗更為豐富。例如,當一個Google地圖用戶沿一個方向拖動地圖時,系統就會在后臺向服務器發出一個異步請求,結果是他能夠在超出屏幕邊界后繼續拖動。這樣以來,當用戶進一步拖動地圖時,新的圖像已經可用了。這導致一種響應更快的感覺。
· 既然跨越基于XMLHttp的到服務器的調用狀態并沒有丟失,那么,AJAX應用程序就可以避免每次都重新生成UI界面。
· 更多的邏輯位于瀏覽器端,從而減少了到Web服務器的來回請求的數量,進而全面改進系統的潛力。
盡管存在這么多的優點,然而AJAX風格的應用程序還是存在一些不足之處。例如,AJAX風格應用程序的開發是比較困難的,因為缺乏相應的框架(一組類似于Windows MFC工具包的UI類)和IDE(調試,可視化設計,等等)支持。另外,基于AJAX進行開發要求一個人必須至少掌握兩種語言(DHTML和JavaScript)。而且,AJAX風格應用程序的編碼需要更長的時間,因為它需要另外的測試以使其支持多瀏覽器版本和類型。最后,由于基于JavaScript的源碼為終端用戶可存取,所以開發過程中的安全分析也變得非常重要。
幸好,例如Atlas,AJAX.NET和Google Maps API等工具的出現為將來構建AJAX風格的應用程序提供了更好的支持。接下來,我們將討論一下,對于構建AJAX風格應用程序的支持技術的發展歷程以及我們能夠從最新發布的工具集Atlas得到怎樣的期望。
讓我們首先討論XMLHttp對象。這個對象最初為微軟所引入,以后在其它平臺(包括Mozilla和蘋果公司的Safari瀏覽器)上也得到實現。XMLHttp支持到Web服務器的異步請求,這樣可以允許客戶端基于JavaScript邏輯調用Web服務器而不需要重載整個頁面。
換句話說,在后臺與Web服務器的交互而不引起整個頁面重載是完全有可能的。
至于XMLHttp對象的使用則相當直接。為簡單起見,讓我們僅考慮IE特定的語法。其實,XMLHttp在其它瀏覽器上的實現語法與這里的討論也很類似。
request = new ActiveXObject("Microsoft.XMLHTTP"); if (request){ request.onreadystatechange = CallbackHandler; request.open("GET", URL, true); request.send(); } function CallbackHandler(){ if ((request.readyState == 4) && (request.status == 200){ string response = request.responseXML; //更新UI的相關部分 } } |
在上面的代碼片斷中,第一步實現實例化Microsoft.XMLHttp類。第二步設置我們剛剛創建的XMLHttp實例的屬性,其中包括當XMLHttp請求完成時將得到控制的回調函數的地址。因為我們在向服務器作異步調用(通過把open方法的第三個參數設置為true來實現),所以我們需要回調函數的地址。在回調函數實現過程中,我們作額外的檢查以確保完成請求。
你從上面的示例代碼中可以看出,以獨立方式使用XMLHttp對象是相當簡單的。然而,把XMLHttp集成到HttpPage生命周期的其它部分中是比較困難的-例如,如何確保服務器端的方法調用能夠存取頁面中其它控件的狀態呢?為了正確初始化這些控件的狀態,服務器端的回調處理需要經歷一個與回調過程類似的HttpPage生命周期。直接使用XMLHttp對象的其它挑戰是,作為開發者,我們需要考慮不同的瀏覽器類型。幸好,ASP.NET 2.0提供了一個可重用的模式-它能夠使得存取回調功能非常容易。注意,隨同ASP.NET 2.0一同發行了若干控件,包括GridView,TreeView等,都綜合利用了回調機制。
圖1描述了ASP.NET 2.0中的回調實現原理。讓我們先看一下服務器端實現原理。首先,在服務器端要定義一個新的接口IcallBackEventHandler。任何ASPX頁面(或打算支持客戶端回調的控件)都需要實現這個ICallBackEventHandler接口。ICallBackEventHandler接口定義了一個稱為RaiseCallbackEvent的方法。這個方法使用一個字符串類型的參數并且返回一個字符串。
圖1.ASP.NET 2.0異步回調機制實現原理 |
在客戶端,為了初始化回調功能,需要調用一個特殊的JavaScript函數。你可以通過調用ClientScriptManager.GetCallbackEventReference來獲得一個到這個特殊的JavaScript函數的引用。到GetCallbackEventReference的調用將產生一個回調引用。當調用此回調函數時,你只需要傳遞一個字符串類型的參數。這是與服務器端的RaiseCallbackEvent簽名一致的。這就是你在客戶端建立回調機制所需做的一切。其它的把客戶端回調函數鉤(hook up)到服務器端的IcallBackEventHandler接口的RaiseCallbackEvent方法的實現則是由框架來完成的。前面提到的初始化回調機制的特殊JavaScript函數使用了另外兩個參數(__CALLBACKPARAM和__CALLBACKID)作為回饋數據,它們分別代表傳遞到調用者的字符串參數和控件的ID。在服務器端,ASP.NET檢測其它兩個參數的存在并且會把請求路由到適當的控件,這將導致調用目標控件上的RaiseCallbackEvent方法。為了解決前面提到的頁面上的控件的初始化問題,ASP.NET運行時刻在服務一次回調時提供了一個簡化版本的HttpPage生命周期。這一周期包括瀏覽頁面初始化的某個具體階段,觀察狀態加載,頁面加載和回調函數事件處理等。一旦回調函數事件被控件所處理,HttpPage生命周期的其它階段就會被跳過。
為了幫助更好地理解ASP.NET 2.0的回調機制,發行包中包括了一個簡單的進度條控件,它依靠回調來決定服務器確定的一項任務的狀態。下面的列表1顯示了該ProgressBar控件的代碼。為了支持客戶端回調函數,這個控件實現了ICallbackEventHandler接口。為了演示之目的,RaiseCallbackEvent方法實現簡單地查找存儲在會話中的一個計數器,每次給計數器加1,并且把新值返回到客戶端。最后,列表2顯示了負責初始化該回調函數的JavaScript代碼。它使用了this.Page.ClientScript.GetCallbackEventReference來獲得一個到需要初始化回調的函數的安全引用。
列表1:ProgressBar.cs
public class ProgressBar : System.Web.UI.Control, System.Web.UI.ICallbackEventHandler{ private int PercentCompleted{ get { if System.Web.HttpContext.Current.Session["PercentComplete"] == null) { System.Web.HttpContext.Current.Session["PercentComplete"] = 1; } else { System.Web.HttpContext.Current.Session["PercentComplete"] =(int)System.Web.HttpContext.Current.Session["PercentComplete"] + 1; } return (int)System.Web.HttpContext.Current.Session["PercentComplete"]; } set { System.Web.HttpContext.Current.Session["PercentComplete"] = 1; } } public string RaiseCallbackEvent(string eventArguments) { int percent = this.PercentCompleted; if (percent > 100) { this.PercentCompleted = 1; return "completed"; } else { return percent.ToString() + "%"; } } protected override void OnPreRender(EventArgs e) { this.Page.ClientScript.RegisterClientScriptBlock(typeof(ProgressBar), "ProgressBar", this.GetClientSideScript(), true); base.OnPreRender(e); } protected override void Render(HtmlTextWriter writer) { System.Text.StringBuilder sb = new StringBuilder(); sb.Append(@"<table id=""ProgressBarContainer"" bgcolor=""LightSteelBlue"" border=""0"" width=""400"" style=""DISPLAY:none; POSITION: absolute; Z-INDEX: 10"">"); sb.Append(@"<tr><td colspan=""3"" style=""padding:3px 2px 2px 10px"">"); sb.Append(@"<font face=""Verdana, Arial, Helvetica, sans-serif"" size=""2"">"); sb.Append(@"<span id=""ProgressBarLabel"">Uploading...</span>"); sb.Append(@"</font></td></tr><tr><td>"); sb.Append(@"<font size=""1""> </font></td><td bgcolor=""#999999"" width=""100%"">"); sb.Append(@"<table id=""ProgressBar"" border=""0"" width=""0"" cellspacing=""0"">"); sb.Append(@"<tr><td style=""background-image:url(progressbar.gif)""> <font size=""1""> </font></td>"); sb.Append(@"</tr></table></td>"); sb.Append(@"<td><font size=""1""> </font></td></tr>"); sb.Append(@"<tr height=""5px""><td colspan=""3""></td></tr>"); sb.Append(@"</table>"); writer.Write(sb.ToString()); base.Render(writer); } private string GetClientSideScript() { System.Reflection.Assembly dll = System.Reflection.Assembly.GetExecutingAssembly(); StreamReader reader; reader = new StreamReader(dll.GetManifestResourceStream("ProgressBar.txt")); StringBuilder js = new StringBuilder(reader.ReadToEnd()); string fp = this.Page.ClientScript.GetCallbackEventReference(this, "", "UpdateProgressBar", ""); js.Replace("##InitiateCallBack##", fp); reader.Close(); return js.ToString(); } } |
列表2:ProgressBar.js
<script language="javascript"> var isCompleted=false; //這個函數初始化到服務器端的回調 function DrawProgressBar(){ ##InitiateCallBack##; if (!isCompleted) { window.setTimeout('DrawProgressBar()',200); } else { isCompleted=false; document.getElementById("ProgressBarContainer").style.display = 'none'; } } //當thecallback完成時,下列函數被調用 function UpdateProgressBar(percent){ if (percent == 'completed'){ isCompleted=true; } else{ document.getElementById("ProgressBar").width = percent; } } |
通過使用在ASP.NET 2.0提供的客戶端回調函數,實現進度條控件是比較直接的,因為在控件和客戶端之間傳遞的數據僅是一個簡單的字符串。然而,一旦我們把其它數據類型也添加到其中,我們就遇到在JavaScript和.NET類型系統之間不匹配的問題。遺憾的是,ASP.NET 2.0中的回調函數實現對此并無多大幫助。任何想使用多種數據類型(簡單類型和復雜類型)的應用程序,都要實現一種自己的定制模式。
幸好,這種限制能夠通過使用一個AJAX.NET開源庫來加以克服,AJAX.NET實現了一種基于代理的方式來調用服務器端函數。AJAX.NET定義了一種稱為AJAXMethod的定制屬性。當一個服務器端方法用AJAXMethod加以修飾時,一個基于JavaScript的客戶端代理將被HttpHandler(它是AJAX.NET庫的一部分)自動生成。不同于ASP.NET 2.0,它支持單個參數的字符串類型以便用于回調實現。AJAX.NET支持整數,字符串,雙精度數,DateTime,DataSet等多種類型。
Bertrand Le Roy建議使用AJAX.NET來處理JavaScript和.NET類型系統之間的差別。他創建了一種稱為EcmaScriptObject的服務器端控件-它基于.NET技術重新創建了JavaScript類型系統。其想法是,用.NET重新生成一種客戶端對象圖。當轉換發生在服務器端時,這種方法顯得更有意義。
即使我們有了一種類型安全的方法來調用回調函數,但是,我們還面臨其它的挑戰。JavaScript擔當起了把AJAX應用程序的各個部分組合到一起的"膠水"的作用。當然,相應地,對JavaScript的依賴性也進一步增加。遺憾的是,盡管JavaScript是一種強有力且通用的語言,但是它并沒有實現面向對象的原則。這意味著,要實現代碼重用可能更為困難。當然,可以使用一些技巧來使JavaScript看上去更象傳統的面向對象語言。不過即使如此,要實現托管語言中的例如事件和代理等特征仍然相當困難。
其它困難還包括:缺乏一個可重用框架來進一步提高JavaScript的開發效率。如果有一種基于JavaScript的能夠隱蔽不同執行環境區別的UI框架或許更好些。另外,如果能夠創建一組類,它們可以用一種安全的方式(相對于手工編碼SOAP包并使用XMLHttp來傳遞它們)來調用Web服務,也會相當不錯。
最近來自微軟的Atlas工程許諾要重點解決這類問題。這是一種極大程度地簡化AJAX風格開發的偉大嘗試。Atlas提供了一種新的JavaScript框架(注意,下面是基于微軟的一次初步宣布,以后有可能發生改變)-UI開發工具包。這其中包括:支持諸如拖放和數據綁定等特征的常用控件;調用Web服務的SOAP棧;隱蔽瀏覽器差別的瀏覽器兼容層;包括例如本地緩沖等內容的客戶端構建模塊。另外,ASP.NET團隊還計劃為ASP.NET開發其它構建模塊,例如配置管理,成員管理等,以便把它們用作Web服務端點,從而實現可以直接從JavaScript中對Web服務進行存取-例如可以容易地從客戶端存取個人信息。最后,Atlas工程還計劃擴展JavaScript語法以便包括接口、生命周期管理和multicast事件。
據說,接下來的幾個月將是令AJAX開發者激動的日子。因此,我非常希望本文能夠激起您對AJAX的興趣,并在你以后構建下一代Web應用程序時優先考慮使用這一技術