MVP For GWT 系列資料轉載十二:徹底瞭解 GWT Part 2:JavaScript 的 overlay type

源文出自:https://txt.appspot.com/pt2club.blogspot.com/2010/02/gwt-part-2javascript-overlay-type.html

徹底瞭解 GWT Part 2:JavaScript 的 overlay type

原文:http://googlewebtoolkit.blogspot.com/2008/08/getting-to-really-know-gwt-part-2.html

技術校正、審閱:tkcn

假設你已經在 GWT module 當中,愉快地使用 JSNI 來呼叫某些手寫的 JavaScript。一切運作正常,但是 JSNI 只能在獨立的 method 下運作。某些整合性狀況需要你徹底地把 JavaScript 跟 Java 的 object 綁在一起——寫 DOM 跟 JSON 就是兩個好例子——所以我們十分需要可以從 Java 程式碼直接與 JavaScript object 互動的方法。換句話說,我們想要 JavaScript 的 object 看起來就像我們寫的 Java object。

GWT 1.5 引入了 JavaScript overlay type,這讓 GWT 程式整合各種 JavaScript object 變得容易許多。這個技術有很多好處,像是讓你能用 Java IDE 的 code completion 跟 refactoring 功能,即使你寫的是 untype 的 JavaScript object。

範例:簡單、有效率的 JSON
用一個範例來了解 overlay type 是最簡單的方法。假設我們要存取一組「customer」數據,底層是用 JSON object。在 JavaScript 中的資料結構可能像這樣:

void jsonData = [

  { "FirstName" : "Ps", "LastName" : "Monkey" },

  { "FirstName" : "痞子", "LastName" : "" },

  { "FirstName" : "Pt2", "LastName" : "Club" },

  { "FirstName" : "STO", "LastName" : "Orz" },

];

 

要把一個 Java type 加到上述的資料結構,要從建立一個 JavaScriptObject 的 subclass 開始,這在 GWT 表示是一個 JavaScript 的 object。接著增加一些 getter。

// An overlay type

class Customer extends JavaScriptObject {

  // Overlay types always have protected, zero-arg ctors

  protected Customer() { }    

 

  // Typically, methods on overlay types are JSNI

  public final native String getFirstName() /*-{ return this.FirstName; }-*/

  public final native String getLastName()  /*-{ return this.LastName;  }-*/

 

  // Note, though, that methods aren't required to be JSNI

  public final String getFullName() {

    return getFirstName() + " " + getLastName(); 

  }

}

 

如此一來,GWT 就會了解所有 Customer 的 instance 實際上是來自 GWT module 以外的 JavaScript object。這包含了很多意義。舉例來說,看到 getFirstName() 跟 getLastName() 裏頭的 this reference。它實質上是代表一個 JavaScript object,所以你操作這個 this 就像在 JavaScript 裏頭一樣。在這個例子中,我們可以直接存取 JSON 中那些我們已知的 field:this.FirstName 跟 this.LastName。

 

那麼,你要如何才能真正得到一個被包裝成 Java type 的 JavaScript object 呢?你不能用 new Customer() 來建構它,因爲重點是把一個既有的 JavaScript object 包裝成 Java type。因此,我們必須使用 JSNI 來得到這樣一個 object:

class MyModuleEntryPoint implements EntryPoint {

  public void onModuleLoad() {

    Customer c = getFirstCustomer();

    // Yay! Now I have a JS object that appears to be a Customer

    Window.alert("Hello, " + c.getFirstName());

  }

 

  // Use JSNI to grab the JSON object we care about

  // The JSON object gets its Java type implicitly 

  // based on the method's return type

  private native Customer getFirstCustomer() /*-{

    // Get a reference to the first customer in the JSON array from earlier

    return $wnd.jsonData[0];

  }-*/;

}

 

現在來搞清楚我們做了啥。我們拿到了一個 plain old JSON object(譯註:源自於 POJO)並且建立一個看起來很正常的 Java type,讓 GWT 程式碼中能夠使用它。於是你就有了 code completion、refactoring、compile 階段的檢查——這些寫 Java 時所擁有的好處。然而,你還是可以靈活地操作任何 JavaScript object,這使得存取 JSON service(使用 RequestBuilder)變得很輕而易舉。

 

爲一些 compiler 強者岔題一下。overlay type 另一個美妙的事情是你可以增加 Java 的 type,但是卻不用影響底層的 JavaScript object。注意到上面例子中,我們加入的 getFullName() 這個 method。它是純粹的 Java 程式碼(並不存在於底層的 JavaScript object),但卻是依照底層 JavaScript object 所寫的。也就是說,處理同一個 JavaScript object,以 Java 的角度會比用 JavaScript 功能豐富得多;而且不用動到底層的 JavaScript object——無論是 instance 或是 prototype。

 

(接續上一段的題外話)在 overlay type 增加 method 這個很酷的怪招是可行的,因爲 overlay type 的設計規則是不允許 polymorphic 呼叫,所有的 method 必須是 final 且/或 private。因此,compiler 是靜態地解讀每一個 overlay type 的 method,所以不需要在 runtime 的時候動態 dispatch。這是爲什麼我們不用拘泥在 object 的 function pointer;compiler 可以直接對 method 呼叫,就好像是 global function、獨立於 object 之外。很明顯的,直接呼叫 function 會比間接快得多。更棒的是,因爲呼叫 overlay type 的 method 是靜態解讀擔廡┒。

 

範例:lightweight collection

 

我們在上面的例子當中掩蓋了某些事情。getFirstCustomer() 這個 method 是非常不切實際的。你一定會希望存取全部的 customer 陣列。所以,我們需要一個 overlay type 來表示這個 JavaScript 陣列。幸運的是,這很簡單:

//泛型在 overlay type 裡頭也運作正常!

class JsArray<E extends JavaScriptObject> extends JavaScriptObject {

  protected JsArray() { }

  public final native int length() /*-{ return this.length; }-*/;

  public final native E get(int i) /*-{ return this[i];     }-*/;

}

 

現在我們可以寫出更有趣的程式了:

class MyModuleEntryPoint implements EntryPoint {

  public void onModuleLoad() {

    JsArray<Customer> cs = getCustomers();

    for (int i = 0, n = cs.length(); i < n; ++i) {

      Window.alert("Hello, " + cs.get(i).getFullName());

    }

  }

 

  // Return the whole JSON array, as is 

private final native JsArray<Customer> getCustomers() /*-{

    return $wnd.jsonData;

  }-*/;

}

 

這是一個很乾淨的程式碼,尤其是以建立靈活配置的角度來看。正如上頭提到的,compiler 可以作一些十分 fancy 的事情,讓它相當有效率。看一下 onModuleLoad() 這個 method 在沒有 obfuscate 的 compile 結果:

function $onModuleLoad(){

  var cs, i, n;

  cs = $wnd.jsonData;

  for (i = 0, n = cs.length; i < n; ++i) {

    $wnd.alert('Hello, ' + (cs[i].FirstName + ' ' + cs[i].LastName));

  }

}

 

這個最佳化真的是 xx 的好。即使是 getFullName() 這個 method 的 overhead 也沒了。事實上, 所有 Java method 的呼叫動作都不見了。當我們說:「GWT 給你可負擔的 abstraction」,這就是其中之一。不僅 inline 的程式碼執行速度明顯變快、我們不再需要定義 function 的內容、也因而得以將 script 簡短化(雖然持平而論,inline 的方式也很容易讓 script 量變多,所以我們小心地在速度與程式碼大小之間取得平衡)。現在回顧上頭原始的 Java 程式碼是十分有趣的,而試著推導 compiler 最佳化的步驟就展示到這邊。 不過,我們還是忍不住要 show 一下對應、obfuscate 過的程式碼: 

function B(){var a,b,c;a=$wnd.jsonData;for(b=0,c=a.length;b<c;++b){  $wnd.alert(l+(a[b].FirstName+m+a[b].LastName))}}

 

注意在這個版本當中,唯一沒有 obfuscate 的是 JavaScript 當中的識別字,例如 FirstName、 LastName、jsonData 等。這是爲什麼即使 GWT 努力讓大量 JavaScript 交互溝通的操作變得容易,但我們還是努力說服別人儘量用純 Java 來寫程式、而不是混著 JavaScript 寫。希望你聽到我們講這些之後,你會明白我們不是要打擊 JavaScript——只是我們不能對它們最佳化,這會讓我們很沮喪。

 

摻在一起作撒尿牛丸 

overlay type GWT 1.5 的重要特性。個技術讓直接與 JavaScript library 互相溝通變得相當容易。希望在讀完這篇文章之後,你可以想像如何以一組 Java type 直接導入任何 JavaScript library GWT 裏頭,進而使用 Java IDE 來進行高效率的開發跟 debug,卻不會因爲任何類型的 GWT overhead 而影響程式碼大小或執行速度。同時,overlay type 作爲一個強大的 abstraction 工具,提供更優雅的低階 API,例如新的DOM package

發佈了33 篇原創文章 · 獲贊 1 · 訪問量 7226
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章