上一篇文章介紹了Dojo1.8帶來的整體上的變化,以及AMD機制帶來的Dojo模塊化的改變。這一篇文章將把中心放在dojo core的改變上。將從dojo base的幾個基本模塊lang,array,load等入手,再詳細介紹一下Event,Advice,Topics,Promises,Requests等幾個常用功能的變化。
相信從上一篇的介紹,大家應該能夠感覺到dojo在模塊化上的變化,baseless是一個趨勢,就是說一個模塊僅僅依賴於它所要使用的模塊,而非去加載一個dojo.js,其實里面有很多的類和方法都是沒有被使用到的。從1.8開始,要盡量避免使用dojo.*,取而代之的將是很多個新的模塊的引入,如lang.*,kernal.*。。。
dojo 基本模塊
首先我們來討論一下Dojo的幾個基本的模塊,或者說一些基本的方法。如dojo.extend,dojo.hitch,dojo.isArray,看看這些基本函數在1.8中將如何被使用。
類型判斷函數
Dojo2.0將移除dojo的幾個類型判斷函數,因為他們基本都可以通過原生的JavaScript函數去完成,如下所示:
| 1.x | 2.0 |
|---|---|
| dojo.isString(v) | typeof v == "string" |
| dojo.isArray(v) | v instanceof Array |
| dojo.isFunction(v) | typeof v == "function" |
| dojo.isArrayLike(v) | "length" in v, etc. (but see note below) |
dojo/_base/lang
| 1.x 語法 | 2.0 模塊 | 2.0 語法 |
|---|---|---|
| dojo.extend | dojo/_base/lang | lang.extend |
| dojo._hitchArgs | dojo/_base/lang | lang._hitchArgs |
| dojo.hitch | dojo/_base/lang | lang.hitch |
| dojo.delegate | dojo/_base/lang | lang.delegate |
| dojo._toArray | dojo/_base/lang | lang._toArray |
| dojo.partial | dojo/_base/lang | lang.partial |
| dojo.clone | dojo/_base/lang | lang.clone |
| dojo.trim | dojo/_base/lang | lang.trim |
| dojo.replace | dojo/_base/lang | lang.replace |
| dojo.mixin | dojo/_base/lang | lang.mixin |
| dojo._mixin | dojo/_base/lang | lang._mixin |
| dojo.exists | dojo/_base/lang | lang.exists |
| dojo.getObject | dojo/_base/lang | lang.getObject |
| dojo.setObject | dojo/_base/lang | lang.setObject |
dojo/_base/array
| 1.x 語法 | 2.0 模塊 | 2.0 語法 |
|---|---|---|
| dojo.forEach | dojo/_base/array | array.forEach |
| dojo.map | dojo/_base/array | array.map |
| dojo.filter | dojo/_base/array | array.filter |
| dojo.every | dojo/_base/array | array.every |
| dojo.some | dojo/_base/array | array.some |
| dojo.indexOf | dojo/_base/array | array.indexOf |
瀏覽器/設備 嗅探器
在dojo1.6中我們可以通過dojo.isIE等變量來獲取用戶瀏覽器的一些信息。而在dojo1.8中將改成使用dojo/has API來獲取這些信息。舉例來說:
if(dojo.isIE < 6){ // ... }
將改成:
require(["dojo/has", "dojo/sniff"], function(has){ if(has("ie") < 6){ // ... } });
Load 和 UnLoad 處理函數
dojo.addOnLoad和dojo.addOnUnLoad函數用來在頁面加載完之后或者離開頁面的時候調用一些處理函數,在1.8中,這兩個函數被分別移到了dojo/ready和dojo/_base/unload模塊之中。用法也發生了如下的變化:
| 1.x 語法 | 2.0 模塊 | 2.0 語法 |
|---|---|---|
| dojo.addOnLoad(f) | dojo/ready | ready(f) |
| dojo.ready(f) | dojo/ready | ready(f) |
| dojo.addOnUnload | dojo/_base/unload | unload.addOnUnload |
| dojo.addOnWindowUnload | dojo/_base/unload | unload.addOnWindowUnload |
我也沒有搞懂為什么ready和unload所在的目錄不一樣,unload要被放在_base目錄中。這里先放一個疑問,之后有空再去研究一下。也希望能有同學發現並告訴我其中的原因。
更多的關於dojo基礎函數的變化可以參考dojo2.0 migration guide的相關內容。
事件處理(Events)
DOM事件
dojo提供了在HTML tag中定義控件/節點DOM事件的方法,下面是一段很常見的例子:
<script> dojo.require("dijit.form.Button"); myOnClick = function(evt){ console.log("I was clicked"); }; dojo.connect(dojo.byId("button3"), "onclick", myOnClick); </script> <body> <div> <button id="button1" type="button" onclick="myOnClick">Button1</button> <button id="button2" data-dojo-type="dijit.form.Button" type="button" data-dojo-props="onClick: myOnClick">Button2</button> <button id="button3" type="button">Button3</button> <button id="button4" data-dojo-type="dijit.form.Button" type="button"> <span>Button4</span> <script type="dojo/connect" data-dojo-event="onClick"> console.log("I was clicked"); </script> </div> </body>
在1.8版本中,將只能使用dojo/on,同樣可以使用編程式或者定義式的方式來定義事件。對DOM原生事件以及Widget的事件都同樣適用。
<script> require(["dojo/dom", "dojo/on", "dojo/parser", "dojo/ready", "dijit/registry", "dijit/form/Button"], function(dom, on, parser, ready, registry){ var myClick = function(evt){ console.log("I was clicked"); }; ready(function(){ parser.parse(); on(dom.byId("button1"), "click", myClick); on(registry.byId("button2"), "click", myClick); }); }); </script> <body> <div> <button id="button1" type="button">Button1</button> <button id="button2" data-dojo-type="dijit/form/Button" type="button">Button2</button> <button id="button3" data-dojo-type="dijit/form/Button" type="button"> <div>Button4</div> <script type="dojo/on" data-dojo-event="click"> console.log("I was clicked"); </script> </button> </div> </body>
dojo/on 取代dojo.connect
在dojo之前的版本中一直被津津樂道的dojo.connect和dojo.disconnect(這兩個函數使用不當會造成嚴重的內存泄漏,后面會專門寫篇文章來講)在1.8之后將會被dojo/on模塊中的on()函數所取代。
dojo.connect用法:
var handle = dojo.connect(node, "onclick", callback); // ... dojo.disconnect(handle);
將被改成如下的新用法:
require(["dojo/on"], function(on){ var handle = on(node, "click", callback); // ... handle.remove(); });
注意到這里的on前綴被移除了,onclick被編程了click。 返回的handle變量提供了一個remove方法,而不再需用使用dojo.disconnect來去除事件關聯。
此外dojo/query返回的節點列表也可以直接使用on函數來綁定事件處理函數。如:
//老的寫法,使用connect dojo.query("li").connect("onclick", callback); //1.8之后,使用on require(["dojo/query"], function(query){ query("li").on("click", callback); });
鼠標事件(mouse)
dojo支持onmouseenter和onmouseleave事件,盡管有些瀏覽器原生不支持這些事件。在dojo1.6及之前的版本中onmouseenter作為string傳入dojo.connect函數中,如:
dojo.connect(node, "onmouseenter", callback);
在1.8中這兩個事件被定義在dojo/mouse模塊的事件對象中,使用的時候它需要和其他模塊一樣加載進來:
require(["dojo/on", "dojo/mouse"], function(on, mouse){ on(node, mouse.enter, callback); });
對於鼠標按鍵的事件的寫法也有所改變。
1.6及之前版本寫法:
dojo.connect(node, "onmousedown", function(evt){ if(dojo.mouseButtons.isLeft(evt){ ... } });
新的寫法如下:
require(["dojo/on", "dojo/mouse"], function(on, mouse){ on(node, "mousedown", function(evt){ if(mouse.isLeft(evt)){ ... } }); });
事件委派(Event Delegation)
對於JavaScript的事件委托一直想找事件好好講一下,幾乎每個框架都提供了對它的支持和封裝,jQuery,dojo等。不過在這里講貌似又不是很合適,先大概講一下自己的理解:
當某個元素的事件被觸發的時候,該事件會被拋到它的父節點(parent)。所以當需要處理很多子節點的事件的時候,我們可以只捕獲父節點的事件,然后通過判斷事件目標(e.target)來判定是不是我們所想要處理的節點的事件被觸發了。等不及的同學可以訪問http://davidwalsh.name/event-delegate這篇文章,我覺得講的還比較詳細。
在dojo1.6中使用dojo.behavior和dojox.NodeList.delegate模塊來支持事件委派,在1.8中被編入了dojo/on模塊中。
1.6的寫法:
var myBehavior = { "#mylist li:click" : { onclick: onListItemClickHandler } }; dojo.behavior.add(myBehavior); dojo.behavior.apply();
1.8及寫法:
require(["dojo/on", "dojo/query", "dojo/_base/window"], function(on, query, win){ on(win.doc(), "#mylist li:click", onListItemClickHandler); });
對於事件處理的更多的變化可以參考下表:
| 1.x 語法 | 2.0 module | 2.0 語法 |
|---|---|---|
| dojo.connect(node,”onclick”,cb) | dojo/on | on(node,”click”,cb) (note that “on” prefix removed) |
| dojo.connect(node,”onmouseenter”,cb) | dojo/on,dojo/mouse | on(node,mouse.enter,cb) |
| dojo.connect(node,”onmouseleave”,cb) | dojo/on,dojo/mouse | on(node,mouse.leave,cb) |
| dojo.connect(node,”onkeypress”,cb) | dojo/on | on(node,”keypress”,cb) for printable or on(node,”keydown”,cb) for arrows etc. |
| dojo.disconnect(handle) | handle.remove() | |
| dojo.connectPublisher | see above | |
| dojo.fixEvent | dojo/_base/event | event.fix |
| dojo.stopEvent | dojo/_base/event | event.stop |
| dojo.mouseButtons.is***() | dojo/mouse | mouse.is***() |
通知(Advice)
Dojo.connect可以用於類似AOP中的后通知(After Advice),可以用於在某些切入點之后調用指定的處理函數。在1.8中,dojo提供了一個較為全面的AOP支持dojo/aspect。
aspect提供了三個方法aspect.before(),aspect.after(),aspect.arround()。分別用來在某個方法調用前、后或者前后插入處理函數的接口。
1.6的寫法:
var handle = dojo.connect(myInstance, "execute", callback); // ... dojo.disconnect(handle);
在1.8中將使用aspect的寫法:
require(["dojo/aspect"], function(aspect){ var handle = aspect.after(myInstance, "execute", callback); / ... handle.remove(); });
訂閱與發布(Subscribe and Publish)/Topic
dojo.publish()/dojo.subscribe()/dojo.unsubscribe是dojo提供的訂閱與發布模式的接口。在1.8中被封裝到了dojo/topic模塊中。
使用老的接口的寫法:
// To publish a topic dojo.publish("some/topic", [1, 2, 3]); // To subscribe to a topic var handle = dojo.subscribe("some/topic", context, callback); // And to unsubscribe from a topic dojo.unsubscribe(handle);
1.8使用Topic的寫法:
require(["dojo/topic"], function(topic){ // To publish a topic topic.publish("some/topic", 1, 2, 3); // To subscribe to a topic var handle = topic.subscribe("some/topic", function(arg1, arg2, arg3){ // ... }); // To unsubscribe from a topic handle.remove(); });
延遲和約定(deferred and promise)
延遲(Deferred)一直是dojo很熱門的一個處理異步調用的工具,它提供了延遲處理函數的調用,直到某個特定事件發生或某個動作完成后發生。Dojo中的Ajax調用的異步處理就是使用的Deffered的機制。
契約(Promise)是一套API接口,定義了進行異步處理的一些方法和屬性,Deferred是promise的實現。后面我會找時間專門寫一篇文章來介紹Dojo中的Deffered和Promise,這里先重點講一下Promise在1.8中的變化。
在Dojo1.8中,對promise接口進行了重構,Dojo.Deferred和dojo.DeferredList的使用也發生了變化。1.8中,dojo.Deferred和dojo.when的功能被移到了dojo/promise,dojo/deferred以及dojo/when的模塊當中。dojo.DeferredList被棄用,相應的功能被dojo/promise/all所取代,可以支持當多個契約事件都發生之后,再調用特定的處理函數。
首先看一下1.6版本Deferred的使用方式:
function createMyDeferred(){ var myDeferred = new dojo.Deferred(); setTimeout(function(){ myDeferred.callback({ success: true }); }, 1000); return myDeferred; } var deferred = createMyDeferred(); deferred.addCallback(function(data){ console.log("Success: ", data); }); deferred.addErrback(function(err){ console.log("Error: ", err); });
再一下1.8的寫法
require(["dojo/Deferred"], function(Deferred){ function createMyDeferred(){ var myDeferred = new Deferred(); setTimeout(function(){ myDeferred.resolve({ success: true }); }, 1000); return myDeferred; } var deferred = createMyDeferred(); deferred.then(function(data){ console.log("Success: ", data); }, function(err){ console.log("Error: ", err); }); });
棄掉了callback函數的調用,取而代之的是resolve方法,這樣更能突出契約的概念。使用then函數通過傳參數的方式傳入處理函數,也比直接調用addCallback和addErrback更加簡潔。
Getting Started with Deferreds 以及 Dojo Deferred and Promises兩篇文檔比較全面的介紹了dojo promise和deferred的概念和用法。大家可以仔細閱讀,之后我也會找時間將這兩篇翻譯一下,並加入自己的理解。有興趣的同學可以關注。
請求(requests)
把請求(request)放在這里講,主要是因為在dojo的Ajax調用使用了promise的接口,所以在1.8中也會隨着promise的改變發生一下使用上的變化。我們取1.6中的dojo.xhrGet方法為例:
dojo.xhrGet({ url: "something.json", handleAs: "json", load: function(response){ console.log("response:", response); }, error: function(err){ console.log("error:", err); } });
在1.8中,新的寫法類似於promise:
require(["dojo/request"], function(request){ request.get("something.json", { handleAs: "json" }).then(function(response){ console.log("response:", response); }, function(err){ console.log("error:", err); }); });
同時在1.8中,對於script以及iframe交互的也被封裝到了dojo/request/script以及dojo/request/iframe兩個子模塊中。使用了與dojo/request的接口。
在官網的文檔上看到這樣一段話:
dojo/request will load the most appropriate request handler for your platform, which for a browser is XHR. The code above could easily be code running on NodeJS and you wouldn't need to change anything.
意思是說dojo/request會自動根據代碼運行的平台和環境選擇最合適的請求處理方式,對於瀏覽器來說當然是XHR。而對於NodeJS這樣的后台運行環境,可能會使用別的方式來處理請求,如請求本地資源的本地IO(對NodeJS了解不是非常深,所以這里只能自己YY一下了。。)。 之前在developworks上看到過dojo部門的同事寫的在Node.js上使用Dojo的文章,還沒來得及實驗,大家有興趣的可以看一下:http://www.ibm.com/developerworks/cn/web/1203_wangpei_dojonodejs/
DOM 操作
接下來我們講一下dojoDOM操作方法和接口的變化。我想大家從前面的介紹中已經看出現在Dojo的趨勢是放棄dojo core作為所有dojo模塊的依賴,所以之前在dojo.*下的一些DOM操作的方法也被相應的移到了一些新的模塊當中。下面的表格總結了這些模塊的以及相應的功能:
| Module | Description | Contains |
|---|---|---|
| dojo/dom | Core DOM functions | byId() isDescendant() setSelectable() |
| dojo/dom-attr | DOM attribute functions | has() get() set() remove() getNodeProp() |
| dojo/dom-class | DOM class functions | contains() add() remove() replace() toggle() |
| dojo/dom-construct | DOM construction functions | toDom() place() create() empty() destroy() |
| dojo/dom-form | Form handling functions | fieldToObject() toObject() toQuery() toJson() |
| dojo/io-query | String processing functions | objectToQuery() queryToObject() |
| dojo/dom-geometry | DOM geometry related functions | position() getMarginBox() setMarginBox() getContentBox() setContentSize() getPadExtents() getBorderExtents() getPadBorderExtents() getMarginExtents() isBodyLtr() docScroll() fixIeBiDiScrollLeft() |
| dojo/dom-prop | DOM property functions | get() set() |
| dojo/dom-style | DOM style functions | getComputedStyle() get() set() |
舉個簡單的例子,在1.6中對一個DOM node的獲取和改變屬性的方法如下:
var node = dojo.byId("someNode"); // Retrieves the value of the "value" DOM attribute var value = dojo.attr(node, "value"); // Sets the value of the "value" DOM attribute dojo.attr(node, "value", "something");
在1.8中,都使用dojo/dom和dojo/dom-attr兩個模塊中的方法:
require(["dojo/dom", "dojo/dom-attr"], function(dom, domAttr){ var node = dom.byId("someNode"); // Retrieves the value of the "value" DOM attribute var value = domAttr.get(node, "value"); // Sets the value of the "value" DOM attribute domAttr.set(node, "value", "something"); });
這樣的寫法引入模塊的概念,可以讓你很明確的明白自己在做的事情,減少錯誤的發生。
好了,這一篇就寫這么多。后面會繼續這一系列的最后一篇,重點介紹一下dojo1.8中對Dijit,dojox以及parser等模塊的變化。敬請關注~~
