http://dojotoolkit.org/documentation/tutorials/1.10/modern_dojo/index.htmljavascript
你可能已經不用doio一段時間了,或者你一直想保持你基於dojo1.6的老代碼在1.10下可以正確運行,但殊不知道應該怎麼作。你一直在據說「AMD」和"baseless"等概念,但殊不知道如何開始學習這些,這篇教程會幫你瞭解這些。html
從dojo的1.7版本開始,dojo Toolkit有了重大的變化,並朝着更加先進的架構模式發展。dojo1.10延續了這樣的發展路線。雖然dojo1.10是向後兼容的,但爲了充分發揮dojo1.10的優點,一些概念也發生了變化。這些概念將成爲dojo2.0的基礎,因此你必須確保你正在正確的學習道路上。因此了可以充分的利用如今dojo的一些新特性,例如dojo/on,你必須理解一些先進的概念。java
在本教程中,咱們會想你介紹dojo新加入的一些概念。我會說起到廢棄的和先進的dojo。我會盡我最大的努力介紹哪些東西發生了變化,爲何變化。一些變化是基本的變化,但也有一些變化時使人難以理解的,不過他們都是爲了讓你的代碼更有效率,運行的更快,更好的利用Javascript,讓你的代碼有更好的可維護性。我以爲花一些時間去了解現代的dojo是十分值得的。node
本教程並非一個dojo變化遷移指南,但不少時候會使你想到你已經熟悉的dojo。一些技術細節能夠在該教程獲取,Dojo 1.X to 2.0 Migration Guide。編程
現代dojo的一個核心概念就是全局命名是不可取的。獲得這樣的結論是有不少緣由的,但在一個複雜的Web應用中,全局命名的變量很容易和各類代碼混雜在一塊兒,特別是使用了多個js框架的時候。站在安全的立場上,我不得不說起,有些不法分子甚至惡意的修改全局變量名稱。現代dojo的解決辦法是若是你想直接訪問全局命名空間,講會被禁止,由於你正在作一件錯誤的事情。雖然鑑於向後兼容的緣由,絕大多數Toolkit仍然保存着全局命名,但在之後的開發中國不要再使用這種方式了。json
若是你發現你的代碼中抱哈了dojo.*、dijit.*或者dojox.*,那麼你已經作錯了一些事情。數組
這意味着儘管開發人員還可使用過期的dojo,例如包含dojo.js,調用一些核心的功能,加載一些模塊,定義自定義模塊等,但咱們是在不推薦你這些繼續作下去了。事實是,這些性能在2.0版本以後都不在支持。promise
Again, repeat after me "the global namespace is bad, the global namespace is bad, I will not use the global namespace, I will not use the global namespace".瀏覽器
另外一個核心概念是,同步操做較慢,異步操做較快。之前的dojo已經使用dojo.Deferred
實現了比較強大的異步加載機制,但在現代dojo中,咱們想最好是全部的操做都是異步的。安全
To strengthen the modularity of Dojo and leverage the concepts above, in 1.7 Dojo adopted the CommonJS module definition called Asynchronous Module Definition (AMD). This meant a fundamental re-write of the Dojo module loader which is usually exposed through the require()
and define()
functions. You can find full documentation of the loader in the reference guide. This has fundamentally changed the way code is structured.
利用上面的概念,咱們能夠增強dojo的模塊化。在dojo1.7版本時,dojo引進了CommonJS定義的模型模塊化概念,叫作異步模塊定義(AMD)。這意味着以前加載和定義模塊的函數requires()和define()函數都要須要重寫。你能夠查看到關於loader in the reference guide的完整文檔資料。這重根本上改變了代碼的結構。
Let's take for example something we would have done in "legacy":
下面咱們是使用過期的dojo方式寫的一個例子。
1 dojo.ready(function(){ 2 dojo.byId("helloworld").innerHTML = "Hello World!"; 3 });
下面咱們再看下如今dojo的定義方式:
1 require(["dojo/dom", "dojo/domReady!"], function(dom){ 2 dom.byId("helloworld").innerHTML = "Hello New World!"; 3 });
歡迎來到完美的新世界,現代dojo的基礎是require()函數。該函數建立了一個javascript閉包,加載代碼須要的模塊,並做爲參數傳遞給主函數。函數的第一個參數是模塊標識集合,第二個參數是主函數。在require()閉包中,咱們能夠經過聲明的參數來引用dojo模塊。咱們調用模塊時,存在一些經常使用的用法習慣。
The loader, just like the legacy one, does all the heavy lifting of finding the module, loading it and managing the loaded modules.
加載器能夠查找模塊,架子啊模塊以及管理已經加載的模塊等重要工做。
你能夠看到,在上面的代碼中,dojo/domReady!模塊雖然在模塊加載的數組中存在,但在參數的列表中卻沒有。其實這是一個加載器插件,用來控制加載器的行爲。該插件的做用是加載等待一直等到在頁面上加載的DOM結構都已經加載完畢。在異步狀況下,你不可能尚未等DOM加載完畢,就去操縱它。因此若是你想對DOM節點作些操做,請確保使用了該插件。由於咱們不直接調用該插件的任何功能,並且該插件是模型標識集合中的最後一個,因此在主函數中,能夠不明確設置該參數。
You can also get a reference to a module with require()
after that module has already loaded, by just supplying the MID as a single string argument. This won't load the module and will throw an error if it isn't already loaded. You won't see this coding style in the Dojo Toolkit, because we like to manage all of our dependencies centrally in the code. But if you choose to use this alternative syntax it would look something like this:
你也可使用require()函數加載一個模塊,但不用把模塊做爲參數傳遞給主函數。若是沒有加載該模塊就直接調用,將會發生錯誤。你不會在Toolkit看到這種代碼風格,由於咱們通常都會集中管理咱們編寫代碼的依賴。但若是你非要採用這種方式話,編寫的代碼以下面的代碼所示:
1 require(["dojo/dom"], function(){ 2 // some code 3 var dom = require("dojo/dom"); 4 // some more code 5 });
當你使用現代Dojo的時候,可能據說過"baseless"的概念。這個概念保證了一個模塊不會依賴任何一個其不須要的其餘模塊。在以前的版本中(dojo1.6及之前),會加載全部的功能,知道2.0版本以後。可是若是你想確保你的代碼能夠很方便的移植到新版本上,你必須中止使用dojo.*這樣的用法。This does mean you might not know where certain parts of the namespace are now。
One of the dojoConfig
options is async
. It defaults to false
and this means all the Dojo base models are automatically loaded. If you set it to true
and take advantage of the asynchronous nature of the loader, these modules will not automatically be loaded. All of this combined together makes for a more responsive and faster loading application.
dojoConfig包含了一個名爲async的屬性。該屬性的默認值爲False,意味着全部的dojo基礎模塊都會被默認加載。若是把該屬性這隻爲True,利用異步加載器,那麼這些模型就不會自動的被加載。這種方式會讓你的應用響應和加載更爲迅速。
另外,dojo支持EcmaScript 5規則。在儘量的狀況下,DOjo會支持ES5的一些功能,但在一些老舊的瀏覽器環境下,dojo可能模擬es5的功能。也就是說看起來像是dojo完成的功能,實際上可能不是dojo作的。
參考指南已經列出了全部的功能,裏面也包含了basic functions。
Once you get outside of the Dojo Base and Core, almost everything else would work like the following. Where you would have done a dojo.require()
:
一旦你在dojo基礎和核心以外編寫代碼,你可能會編寫下面的代碼。你會使用dojo.require()函數。
1 dojo.require("dojo.string"); 2 dojo.byId("someNode").innerHTML = dojo.string.trim(" I Like Trim Strings ");
但如今,你能夠直接使用require()函數。
1 require(["dojo/dom", "dojo/string", "dojo/domReady!"], function(dom, string){ 2 dom.byId("someNode").innerHTML = string.trim(" I Like Trim Strings "); 3 });
dojo.connection()和dojo.disconnection()已經被移動大dojo/_base/connect模塊中,如今dojo必須使用dojo/on來處理事件,使用dojo/aspect來通知函數。在Events教程中有更詳細的信息,但咱們在這裏主要對比他們之間的差異。在之前的dojo中,在事件和函數通知上,沒有太大的區別,咱們使用使用dojo.connect()處理這兩種狀況。事件是當一個對象觸發一個動做時關聯的哈數,例如click事件。dojo/on能夠無縫的處理原生態的DOM事件以及dojo小部件暴露出的事件。把一個行位連接到已有對象上也是AOP(面向方面編程)重要概念的一部分。不少dojo部分經過dojo/sapect模塊提供的功能相應了AOP的思想。
在之前的dojo中,咱們處理Button的Onclick事件的代碼以下:
1 <script> 2 dojo.require("dijit.form.Button"); 3 4 myOnClick = function(evt){ 5 console.log("I was clicked"); 6 }; 7 8 dojo.connect(dojo.byId("button3"), "onclick", myOnClick); 9 </script> 10 <body> 11 <div> 12 <button id="button1" type="button" onclick="myOnClick">Button1</button> 13 <button id="button2" data-dojo-type="dijit.form.Button" type="button" 14 data-dojo-props="onClick: myOnClick">Button2</button> 15 <button id="button3" type="button">Button3</button> 16 <button id="button4" data-dojo-type="dijit.form.Button" type="button"> 17 <span>Button4</span> 18 <script type="dojo/connect" data-dojo-event="onClick"> 19 console.log("I was clicked"); 20 </script> 21 </div> 22 </body>
在現代的dojo中,你只須要使用dojo/on模塊就能夠實現上述功能。不管是編程的方式仍是聲明的方式,不管是原生態DOM事件仍是小部件定義的事件。
1 <script> 2 require([ 3 "dojo/dom", 4 "dojo/on", 5 "dojo/parser", 6 "dijit/registry", 7 "dijit/form/Button", 8 "dojo/domReady!" 9 ], function(dom, on, parser, registry){ 10 var myClick = function(evt){ 11 console.log("I was clicked"); 12 }; 13 14 parser.parse(); 15 16 on(dom.byId("button1"), "click", myClick); 17 on(registry.byId("button2"), "click", myClick); 18 }); 19 </script> 20 <body> 21 <div> 22 <button id="button1" type="button">Button1</button> 23 <button id="button2" data-dojo-type="dijit/form/Button" type="button">Button2</button> 24 <button id="button3" data-dojo-type="dijit/form/Button" type="button"> 25 <div>Button4</div> 26 <script type="dojo/on" data-dojo-event="click"> 27 console.log("I was clicked"); 28 </script> 29 </button> 30 </div> 31 </body>
Notice how dijit.byId
isn't used. In "modern" Dojo, the dijit/registry
is used for widgets and registry.byId()
retrieves a reference to the widget. Also, notice how dojo/on
handles both the DOM node and widget events in the same way.
你會先發現咱們沒有用到dijit.byId函數。在現代dojo中,已經使用dijit/refistry模塊中的registry.byId()函數來獲取小部件的引用。而且使用dojo/on模塊使用同一種方式處理原生態DOM和小部件的事件。
若是要解除一個事件綁定的話,使用之前的dojo,你可能會編寫下面的代碼:
1 var callback = function(){ 2 // ... 3 }; 4 var handle = dojo.connect(myInstance, "execute", callback); 5 // ... 6 dojo.disconnect(handle);
In "modern" Dojo, the dojo/aspect
module allows you to get advice from a method and add behaviour "before", "after" or "around" another method. Typically, if you were converting a dojo.connect()
you would replace it with an aspect.after()
which would look something like this:
在現代dojo中,使用dojo/aspect模塊,容許你獲得一個函數運行以前,以後的行爲引用。你可使用aspect.after替換dojo.connect(),代碼以下所示:
1 require(["dojo/aspect"], function(aspect){ 2 var callback = function(){ 3 // ... 4 }; 5 var handle = aspect.after(myInstance, "execute", callback); 6 // ... 7 handle.remove(); 8 });
Another area that has undergone a bit of a revision is the "publish/subscribe" functionality in Dojo. This has been modularized under the dojo/topic
module and improved.
For example, the "legacy" way of doing this would be something like:
另一個有重大修訂的模塊時發佈和訂閱模塊。該模塊已經被整合到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);
但爲現代dojo中,你須要利用dojo/topic,按照下面的方式作。
1 require(["dojo/topic"], function(topic){ 2 // To publish a topic 3 topic.publish("some/topic", 1, 2, 3); 4 5 // To subscribe to a topic 6 var handle = topic.subscribe("some/topic", function(arg1, arg2, arg3){ 7 // ... 8 }); 9 10 // To unsubscribe from a topic 11 handle.remove(); 12 });
dojo中,一個最重要的核心概念之一就是延遲類,在1.5版本中,該功能已經從遷移到「promise」中,它值得咱們在這裏討論一下。在1.8版本時,promise類被重寫。雖然重表面的語義上看,和之前同樣,但該模塊已經不在支持老舊的API,因此如何想使用該模塊,必須使用現代Dojo的API。在老舊API中,實現相關功能的代碼以下:
1 function createMyDeferred(){ 2 var myDeferred = new dojo.Deferred(); 3 setTimeout(function(){ 4 myDeferred.callback({ success: true }); 5 }, 1000); 6 return myDeferred; 7 } 8 9 var deferred = createMyDeferred(); 10 deferred.addCallback(function(data){ 11 console.log("Success: " + data); 12 }); 13 deferred.addErrback(function(err){ 14 console.log("Error: " + err); 15 });
但在現代dojo中,實現的代碼是這樣的
1 require(["dojo/Deferred"], function(Deferred){ 2 function createMyDeferred(){ 3 var myDeferred = new Deferred(); 4 setTimeout(function(){ 5 myDeferred.resolve({ success: true }); 6 }, 1000); 7 return myDeferred; 8 } 9 10 var deferred = createMyDeferred(); 11 deferred.then(function(data){ 12 console.log("Success: " + data); 13 }, function(err){ 14 console.log("Error: " + err); 15 }); 16 });
Ajax是每一個javascript庫的核心模塊之一。從dojo1.8版本以後,該模塊的API也進行了更新,能夠跨平臺、易擴展並易重用。之前,你常常兼顧處理來自XHR、腳本以及IFrameIO以及本身的程序返回的數據。dojo/request模塊就可讓這些事情變得更簡單。
Just like dojo/promise
the old implementations are still there, but you can easily re-factor your code to take advantage of the new. For example, in "legacy" Dojo you might have written something like this:
相似於dojo/promise
,之前的代碼仍然可用,但你能夠很難容易的重構你的代碼。例如,使用之前的dojo,你寫的代碼以下所示。
1 dojo.xhrGet({ 2 url: "something.json", 3 handleAs: "json", 4 load: function(response){ 5 console.log("response:", response); 6 }, 7 error: function(err){ 8 console.log("error:", err); 9 } 10 });
使用現代Dojo,代碼以下:
1 require(["dojo/request"], function(request){ 2 request.get("something.json", { 3 handleAs: "json" 4 }).then(function(response){ 5 console.log("response:", response); 6 }, function(err){ 7 console.log("error:", err); 8 }); 9 });
You might be seeing a trend here if you have gotten this far in the tutorial, in that not only has Dojo abandoned its dependency on the global namespace and adopted some new patterns, it has also broken out some of "core" functionality into modules and what is more core to a JavaScript toolkit than DOM manipulation.
在整個教程中,您能夠看到了dojo的整個發展趨勢,不只僅是把全局命名廢棄,更重要的是基於模塊化架構定義不少核心模塊。例如對DOM的操做模塊。
Well, that too has been broken up into much smaller chunks and modularized. Here is summary of the modules and what they contain:
DOM操做模塊被分解爲不少下的模塊,主要的模塊以下表所示:
Module
描述 | 包含的主要函數 | |
---|---|---|
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() |
有一點一直貫穿整個現代DOJO的設計思路,那就是工具包的業務邏輯和訪問是分離的。
以下面的代碼所示:
1 var node = dojo.byId("someNode"); 2 3 // Retrieves the value of the "value" DOM attribute 4 var value = dojo.attr(node, "value"); 5 6 // Sets the value of the "value" DOM attribute 7 dojo.attr(node, "value", "something");
現代DOJO實現上面的功能代碼:
1 require(["dojo/dom", "dojo/dom-attr"], function(dom, domAttr){ 2 var node = dom.byId("someNode"); 3 4 // Retrieves the value of the "value" DOM attribute 5 var value = domAttr.get(node, "value"); 6 7 // Sets the value of the "value" DOM attribute 8 domAttr.set(node, "value", "something"); 9 });
在這個現代dojo的例子中,很是清晰的展現了代碼所作的工做。由於沒有響應的參數傳入,你編寫的代碼很難運行出不是你本意的效果。這種訪問方式隔離機制一直貫穿這個現代dojo的設計思想。
在dojo1.6時,新的API dojo/store被加入,而dojo/data API被廢棄。但dojo/data的數據存儲以及dojox/data數據存儲相關的功能一直在dojo2.0以前均可以使用。但咱們建議仍是使用的新的API。本教程不能展開詳細的介紹該概念,但你能夠在Dojo Object Store教程中查看更詳細的信息。
dijit爲了適應如今dojo世界,也進行了一些重構。但不少重構都已經在toolkit包的基礎部分完成,主要的重構目標是把功能分解成一個個小的積木,而後再粘合在一塊兒,完成複雜的功能。若是你正在建立一個自定義的小部件,你能夠閱讀Creating a custom widget教程。
If you are just developing with dijits or other widgets, then there were a few core concepts that were introduced with the dojo/Stateful
and dojo/Evented
classes.
dojo/Stateful
provides discrete accessors for widget attributes as well as the ability to "watch" changes to these attributes. For example, you can do the following:
若是你只是使用dijits或者其餘的小部件進行開發,這兒有一些核心的概念須要介紹一下,dojo/Stateful
and dojo/Evented
。
dojo/Stateful模塊提供了訪問小布家眷性的接口,而且能夠監測小部件屬性的變化,例如:
1 require(["dijit/form/Button", "dojo/domReady!"], function(Button){ 2 var button = new Button({ 3 label: "A label" 4 }, "someNode"); 5 6 // Sets up a watch on button.label 7 var handle = button.watch("label", function(attr, oldValue, newValue){ 8 console.log("button." + attr + " changed from '" + oldValue + "' to '" + newValue + "'"); 9 }); 10 11 // Gets the current label 12 var label = button.get("label"); 13 console.log("button's current label: " + label); 14 15 // This changes the value and should call the watch 16 button.set("label", "A different label"); 17 18 // This will stop watching button.label 19 handle.unwatch(); 20 21 button.set("label", "Even more different"); 22 });
dojo/Evented
provides emit()
and on()
functionality for classes and this is incorporated into Dijits and widgets. In particular, it is "modern" to use widget.on()
to set your event handling. For example, you can do the following:
dojo/Evented爲對象提供了emit()和on()函數,並讓着兩個函數成爲dijits和小部件的一部分。在現代dojo代碼中,會使用widget.on()函數去處理事件。例以下面的代碼:
1 require(["dijit/form/Button", "dojo/domReady!"], function(Button){ 2 var button = new Button({ 3 label: "Click Me!" 4 }, "someNode"); 5 6 // Sets the event handling for the button 7 button.on("click", function(e){ 8 console.log("I was clicked!", e); 9 }); 10 });
最後是dojo/parser。dojo包含了編碼式和聲明式兩種應用方式。使用dojo/paeser就能夠把聲明式的代碼轉換成dojo組件或對象。現代dojo的一些思想也已經對該組件產生了影響,並進行了重構。
doji仍然支持parseOnLoad:true這個配置,但更多的時候會有更明確的定義方式,例如:
1 require(["dojo/parser", "dojo/domReady!"], function(parser){ 2 parser.parse(); 3 });
dojo/parser模塊的一個重大變化就是支持HTML5,並容許你標記HTML5節點。特別是把dojoType屬性修改成data-dojo-type,而且代替非標準html的屬性數據。全部的這些參數都會被傳遞給對象的構造函數。例如:
1 <button data-dojo-type="dijit/form/Button" tabIndex=2 2 data-dojo-props="iconClass: 'checkmark'">OK</button>
dojo支持在data-dojo-type屬性上設置模塊的ID。例如dojoType="dijit.form.Button"
變成了data-dojo-type="dijit/form/Button"。
在上述的dojo/Evented和dojo/Stateful的變化中有Watch和on函數功能,目前daojo/parser也已經響應了這些變化。例如:
1 <button data-dojo-type="dijit/form/Button" type="button"> 2 <span>Click</span> 3 <script type="dojo/on" data-dojo-event="click" data-dojo-args="e"> 4 console.log("I was clicked!", e); 5 this.set("label", "Clicked!"); 6 </script> 7 <script type="dojo/watch" data-dojo-prop="label" data-dojo-args="prop, oldValue, newValue"> 8 console.log("button: " + prop + " changed from '" + oldValue + "' to '" + newValue + "'"); 9 </script> 10 </button>
另外,parser也支持dojo/asoect的相關概念,因此你能夠在上面的代碼中使用Before、after等操做。更詳細的信息可參考dojo/parser。
The dojo/parser
also supports auto-requiring in modules. This means you don't necessairly have to require in the module before invoking the require. If you set isDebug
to true
though, it will warn you if you are requiring modules this way
The final area to briefly touch on in this tutorial is the Dojo builder. In Dojo 1.7 it was completely rewritten. Partly it was to handle the significant changes with AMD, but it was also designed to modernize it and make it very feature rich. It is a topic too vast for this tutorial. You should read the Creating Builds tutorial for information, but be prepared to forget everything you knew about the old builder in order to embrace the "modern" builder.
最後的部分,咱們討論下dojo構建器,在dojo1.7時,該部分被重寫。一部分變化時爲了適應AMD架構,但其新的架構模式讓其對外能夠提供更加豐富的功能。這是一個很龐大的話題。你能夠在Creating Builds教程中看到更多的詳細信息。可是你要準備好忘掉一切舊的應用模式,擁抱新的現代的dojo構造器。
但願你對進入現代dojo世界特別感興趣,雖然須要您花費一些精力忘記舊的用法,去開始新的dojo之路,但一旦你開始行動,你就很難在回去了,你會發現已經開始使用模塊化思想開發您的應用程序。
請記住現代dojo的使用方式:
get()
and a set()
for what you want to do.