成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

如何開發屬于自己的框架

譯文
開發 前端
我們原本希望尋找一套框架以幫助The Daily Mail網站構建起新的內容管理系統。項目的主要目標在于保證編輯流程與文章中的所有元素實現深度交互(包括圖片、嵌入對象以及呼出對話框等等),并使其能夠支持拖拽操作、模擬化與自我管理等功能。

對于大多數開發人員來說,從零開始構建一套框架聽起來似乎有些陌生甚至聞所未聞。這么干的家伙肯定是瘋了,對吧?既然市面上已經堆滿了各式各樣的JavaScript框架,為什么我們還要費力搞出自己的一套東西?

我們原本希望尋找一套框架以幫助The Daily Mail網站構建起新的內容管理系統。項目的主要目標在于保證編輯流程與文章中的所有元素實現深度交互(包括圖片、嵌入對象以及呼出對話框等等),并使其能夠支持拖拽操作、模擬化與自我管理等功能。

當下我們所能獲取到的所有框架都或多或少地以開發人員所定義的靜態用戶界面為設計基礎。而我們則既需要可編輯文本,又需要動態渲染UI元素。

Backbone的定位實在太低級了一些,它最多只能提供基本對象結構與消息收發機制。我們需要在此基礎上建立大量抽象關系才能滿足自身的實際需要,考慮到這一點、我們決定自己動手重新構建基礎。

我們還曾決定利用AngularJS框架來構建采用相對靜態UI的中小型瀏覽器應用程序。不過遺憾的是,AngularJS是一套徹頭徹尾的黑盒環境——它不會將任何便捷的API擴展或者能夠服務于我們創建對象的操作機制直接擺在臺面上——指令、控制器、服務皆是如此。再有,盡管AngularJS能夠在view與scope表達之間建立響應式連接,但卻不允許我們在不同模式下對這些響應式連接進行定義,因此任何一款中型應用程序都會變得像jQuery應用那樣充斥著事件監聽器(listener)與回調機制。二者之間的惟一區別就是,AngularJS框架會利用watcher取代listener、我們所操作的也將是scope而非DOM。

經過總結,我們理想中的框架需要滿足以下條件:

• 能夠以聲明方式實現應用程序開發,并能為view帶來響應式綁定模式。

• 能夠在應用程序內的不同模型之間建立響應式數據綁定,從而通過聲明而非指令方式對數據擴展加以管理。

• 在綁定結構中插入校驗與翻譯機制,這樣我們就能將view與數據模型加以對接、而不必再通過AngularJS進行查看。

• 對與DOM元素相對接的組件進行精確控制。

• View管理的靈活性允許開發者自動操作DOM變更,并在渲染比DOM操作效率更高時利用實例中的任意模板引擎對某些部分進行重新渲染。

• 擁有動態創建UI的能力。

• 有能力觸及數據響應背后的深層機制并精確控制view更新與數據流。

• 能夠對該框架提供的組件進行功能性擴展,并創建新的組件。

在現有解決方案中,我們實在找不到能夠滿足需求的選項。在這種情況下,我們決定以并行方式開發Milo,并將其作為應用程序的構建基礎。

為什么選擇Milo這個名稱?

之所以選擇Milo這個名稱,是由于Milo Minderbinder這位由Joseph Heller撰寫的《第二十二條軍規》一書中的戰爭商人。盡管是從軍營中的炊事工作起步,但他很快建立起了屬于自己的暴利貿易企業,并將成果與每一個人“共享”。

作為一套框架,Milo具備模塊接駁機制(Binder),能夠將DOM元素與組件相對接(通過特殊的ml-bind屬性)。而且這套模塊接駁機制還允許我們在不同數據源之間建立實時響應式連接(Model與Data類組件正是這樣的數據源)。

巧合的是,Milo正好也是MaIL Online網站的縮寫,而且要不是為了給Mail Online打造獨特的運作環境、我們永遠也不會創建出這樣一套框架。

管理view

Binder

Milo當中的view由組件負責管理,而這些組件基本上可以算是JavaScript類的各項實例、分別管理與之對應的DOM元素。很多框架都會利用組件還作為UI元素管理概念,但其中最引人注目的無疑要數Ext JS。我們在很多場景中使用過Ext JS(我們要替換掉的遺留應用就是用它構建起來的),但同時也發現了兩種令人避之而惟恐不速的缺陷。

This is where binder comes in.首先,Ext JS讓我們無法輕松對標記加以管理。構建一套UI的惟一方式就是將所有組件配置放在一起進行分層嵌套。這種作法會給標記渲染帶來不必要的復雜性,開發人員也將失去應有的控制能力。我們需要一種能夠以內聯方式手動制作HTML標記并創建組件的方法。

Binder會掃描我們的標記并從中查找ml-bind屬性,這樣它才能讓組件完成實例化并將其與對應元素相綁定。該屬性中包含著與對應組件相關的信息;其中可能包括組件 class、facet以及必須具備的組件名稱。

 

  1. <div ml-bind=”ComponentClass[facet1, facet2]:componentName”> 
  2.   Our milo component  
  3. </div> 

 

我們會在接下來的部分進一步討論facet,但目前讓我們先看看該如何獲取這項屬性值并利用一條正則表達式將其提取出來。

 

  1. var bindAttrRegex = /^([^\:\[\]]*)(?:\[([^\:\[\]]*)\])?\:?([^:]*)$/;  
  2.    
  3. var result = value.match(bindAttrRegex);  
  4. // result is an array with  
  5. // result[0] = ‘ComponentClass[facet1, facet2]:componentName’;  
  6. // result[1] = ‘ComponentClass’;  
  7. // result[2] = ‘facet1, facet2’;  
  8. // result[3] = ‘componentName’; 

 

有了這些信息,我們接下來要做的就是對整個ml-bind屬性進行遍歷、提取此類值并通過創建實例對各個元素加以管理。

  1. var bindAttrRegex = /^([^\:\[\]]*)(?:\[([^\:\[\]]*)\])?\:?([^:]*)$/;  
  2.    
  3. function binder(callback) {  
  4.     var scope = {};  
  5.        
  6.     // we get all of the elements with the ml-bind attribute  
  7.     var els = document.querySelectorAll('[ml-bind]');  
  8.     Array.prototype.forEach.call(els, function(el) {  
  9.         var attrText = el.getAttribute('ml-bind');  
  10.         var result = attrText.match(bindAttrRegex);  
  11.            
  12.         var className = result[1] || 'Component';  
  13.         var facets = result[2].split(',');  
  14.         var compName = results[3];  
  15.            
  16.         // assuming we have a registry object of all our classes  
  17.         var comp = new classRegistry[className](el);  
  18.         comp.addFacets(facets);  
  19.         comp.name = compName;  
  20.         scope[compName] = comp;  
  21.            
  22.         // we keep a reference to the component on the element  
  23.         el.___milo_component = comp;  
  24.     });  
  25.        
  26.     callback(scope);  
  27. }  
  28.    
  29. binder(function(scope){  
  30.     console.log(scope);   
  31. }); 

所以只需要利用正則表達式與DOM遍歷,大家就可以利用自定義語法創建出符合特定業務邏輯與背景需求的迷你框架。只需少許代碼,我們已經設置出一套能夠容納模塊化、自我管理組件的架構,并能夠隨意加以運用。我們還可以創建出便捷的聲明式語法、從而對HTML中的組件進行實例化及配置;不過與AngularJS不同,我們能夠根據實際需求對這些組件進行管理。

#p#

由職責驅動的設計方案

Ext JS令人不滿的第二大原因在于,它所采用的類結構在分層方面極具剛性且存在跳躍性,這使其很難與我們的組件類實現有機結合。我們希望編寫出一套全面的特性列表,一篇文章中可能包含的任何組件都能從中找到對應特性。舉例來說,某個組件具備可編輯特性,可以作為事件加以監聽,甚至能夠成為拖拽目標或者本身就屬于拖拽對象。這還只是所需特性當中的一小部分。我們在初步名單中包含了大約十五種不同的功能類型,囊括了多數特定組件有可能用到的特性。

將這些特性整理成為某種分層式結構不僅僅令人頭痛,同時也會給我們對特定組件類的功能變更帶來嚴重局限(我們隨后投入大量精力處理這個問題)。有鑒于此,我們決定采取另一套更具靈活性的面向對象設計方案。

我們已經了解過由職責驅動的設計方案。與最常見的類及其所包含數據特性定義模式相反,職責驅動設計更多將關注重點放在對象負責執行的操作身上。由于我們需要處理的是復雜度頗高且無法預測的數據模型,因此這種方式無疑非常合適。這類解決方案還允許我們日后根據實際需要對細節加以調整。

在職責驅動設計當中,我們摒棄了“角色(Role)”這一關鍵性組成部分。一個角色指的是一系列相關責任的集合。在我們的項目當中,我們將角色設定為編輯、拖拽、拖拽區、可選或者事件等等。但大家要如何將這些角色體現在代碼當中?考慮到這一點,我們借鑒了裝飾者模式的作法。

裝飾者模式允許我們將特性添加到單一對象當中,無論靜態或者動態,而且完全不會對同一類中其它對象的特性造成影響。盡管類特性的運行時操作在此次項目中并非必要,但我們對這種處理思路所提供的封裝方式很感興趣。Milo在具體實施過程中采取了混合型方式,即將被稱為facet的對象作為屬性附加到該組件實例當中。作為配置對象,facet會引用該組件、而組件則是facet的“持有者”。這種機制允許我們根據每個組件類對facet加以定制。

大家可以將facet視為高級、可配置混合類型,它們在持有者對象上擁有自己的命名空間甚至是自己的init方法——該方法需要被該facet子類所覆蓋。

  1. function Facet(owner, config) {  
  2.     this.name = this.constructor.name.toLowerCase();  
  3.     this.owner = owner;  
  4.     this.config = config || {};  
  5.     this.init.apply(this, arguments);  
  6. }  
  7. Facet.prototype.init = function Facet$init() {};  

因此我們可以將這個示例Facet類劃入子類并為自己需要的每種特性類型創建特定facet。Milo預先內置有多種不同類型的facet,例如DOM facet、負責提供能夠在其持有者組件元素上執行的DOM功能集合,外加List與Item facet、二者能夠共同創建出重復組件列表。

這些facet隨后會被我們稱為的FacetedObject類匯聚到一起,這是一個抽象類、繼承自全部組件。FacetedObject還擁有一個名為createFacetedClass的類方法,能夠將其自身劃入子類并將所有包含facets屬性的facet附加至該類。通過這種方式,當FacetedObject轉化為實例后就會接入其全部facet類,并可以通過迭代方式實現組件引導。

  1. function FacetedObject(facetsOptions /*, other init args */) {  
  2.    
  3.     facetsOptions = facetsOptions ? _.clone(facetsOptions) : {};  
  4.    
  5.     var thisClass = this.constructor  
  6.         , facets = {};  
  7.    
  8.     if (! thisClass.prototype.facets)  
  9.         throw new Error('No facets defined');  
  10.    
  11.     _.eachKey(this.facets, instantiateFacet, thistrue);  
  12.    
  13.     Object.defineProperties(this, facets);  
  14.    
  15.     if (this.init)  
  16.         this.init.apply(this, arguments);  
  17.    
  18.     function instantiateFacet(facetClass, fct) {  
  19.         var facetOpts = facetsOptions[fct];  
  20.         delete facetsOptions[fct];  
  21.    
  22.         facets[fct] = {  
  23.             enumerable: false,  
  24.             value: new facetClass(this, facetOpts)  
  25.         };  
  26.     }  
  27. }  
  28.    
  29. FacetedObject.createFacetedClass = function (name, facetsClasses) {  
  30.     var FacetedClass = _.createSubclass(this, name, true);  
  31.    
  32.     _.extendProto(FacetedClass, {  
  33.         facets: facetsClasses  
  34.     });  
  35.     return FacetedClass;  
  36. };  

在Milo中,我們還創建出一個包含匹配createComponentClass類方法的基礎Component類、旨在進一步實現抽象化,不過其基本原則仍然保持不變。由于關鍵特性仍然由可配置facet負責管理,我們可以在一種聲明樣式內創建多種不同組件類,而且完全不必編寫太多自定義代碼。下面來看如何在Milo當中使用一部分可以直接使用的facet。

  1. var Panel = Component.createComponentClass(‘Panel’, {  
  2.     dom: {  
  3.         cls: ‘my-panel’,  
  4.         tagName: ‘div’  
  5.     },  
  6.     events: {  
  7.         messages: {‘click’: onPanelClick}  
  8.     },  
  9.     drag: {messages: {...},  
  10.     drop: {messages: {...},  
  11.     container: undefined  
  12. });  

在這里,我們已經創建出一個名為Panel的組件類且已經與DOM功能方法相對接,它會在init上自動設置其CSS類、能夠監聽DOM事件并在init上設置點擊處理程序、能夠作為拖拽對象也可以作為拖拽目標。作為最后一個facet,container負責確保該組件對自己的scope進行設置并擁有實際起效的子組件機制。

Scope

接下來的話題讓我們討論了很長一段時間,即附加至文檔的所有組件到底應該采用扁平化結構還是樹狀結構——在樹狀結構中,子分支只能接受來自父分支的訪問。

在某些情況下,我們肯定需要scope機制的介入,但其更多涉及到的是實施層而非框架層。舉例來說,我們擁有多套包含有圖片的圖片組。這些組能夠輕松追蹤其子圖片的當前狀態,而無需涉及通用scope。

我們最終決定在文檔中建立一套組件scope樹狀結構。引入scope能讓很多工作變得更易于打理,也讓我們可以使用更多通用組件命名方式,但這種機制顯然需要加以管理。如果大家刪除了某個組件,則必須將其從父scope當中移除出去。如果大家對某個組件進行移動,則必須將其由原scope內移除再添加至另一個scope當中。

事實上,scope是一種特殊的散列或者映射對象,scope當中容納的第一個子分支都擁有該對象的屬性。在Milo當中,scope存在于容器facet之上,而后者本身幾乎沒有任何功能性可言。不過scope對象卻擁有一系列不同類型的方法,能夠對自身進行操作與迭代。但為了避免命名空間沖突,這些方法在命名時都會以下劃線作為起始字符。

  1. var scope = myComponent.container.scope;  
  2.    
  3. scope._each(function(childComp) {  
  4.     // iterate each child component  
  5. });  
  6.    
  7. // access a specific component on the scope  
  8. var testComp = scope.testComp;  
  9.    
  10. // get the total number of child components  
  11. var total = scope._length();  
  12.    
  13. // add a new component ot the scope  
  14. scope._add(newComp);  

#p#

消息收發——同步與異步

我們希望能在不同組件之間實現松散耦合,因此我們決定將消息收發功能附加到所有組件與facet之上。

消息機制實施工作的第一步在于建立起方法集合,旨在對訂閱者數組進行管理。方法與數組以混合方式存在于對象當中,并借此實現消息收發功能。

對于消息機制實施方案的第一步,我們姑且采用一套簡化版本,具體代碼如下所示:

  1. var messengerMixin =  {  
  2.     initMessenger: initMessenger,  
  3.     on: on,  
  4.     off: off,  
  5.     postMessage: postMessage  
  6. };  
  7.    
  8.    
  9. function initMessenger() {  
  10.     this._subscribers = {};  
  11. }  
  12.    
  13. function on(message, subscriber) {  
  14.     var msgSubscribers = this._subscribers[message] =  
  15.         this._subscribers[message] || [];  
  16.    
  17.     if (msgSubscribers.indexOf(subscriber) == -1)  
  18.         msgSubscribers.push(subscriber);  
  19. }  
  20.    
  21. function off(message, subscriber) {  
  22.     var msgSubscribers = this._subscribers[message];  
  23.     if (msgSubscribers) {  
  24.         if (subscriber)  
  25.             _.spliceItem(msgSubscribers, subscriber);  
  26.         else 
  27.             delete this._subscribers[message];  
  28.     }  
  29. }  
  30.    
  31. function postMessage(message, data) {  
  32.     var msgSubscribers = this._subscribers[message];  
  33.     if (msgSubscribers)  
  34.         msgSubscribers.forEach(function(subscriber) {  
  35.             subscriber.call(this, message, data);  
  36.         });  

任何使用這種混合類型的對象都能利用postMessage方法自行實現消息收發(由對象本身或者任何其它代碼實現),而該代碼的訂閱功能可通過其它擁有相同名稱的方法實現開啟與關閉。

現在,我們的消息機制已經具備以下特性:

• 附加外部消息來源(包括DOM消息、窗口消息、數據變更以及其它消息機制等)——例如由Events facet利用其通過Milo消息機制來顯示DOM事件。這項功能依靠單獨的MessageSource類及其子類實現。

• 定義定制化消息收發API,從而將消息與外部消息數據翻譯成內部消息。例如Data facet就能利用其翻譯變更內容,并將DOM事件輸入到數據變更事件(詳見下文model)當中。這項功能領先單獨的MessengerAPI類及其子類實現。

• 模式訂閱(利用正則表達式)例如model(如下所示)利用內部模式訂閱以實現深層model變更訂閱。

• 利用以下語法作為訂閱機制的組成部分,進而實現背景信息(即訂閱者中的對應值)定義:

  1. component.on('stateready',  
  2.     { subscriber: func, context: context }); 

• 利用once方法創建只發送一次的訂閱機制。

• 在postMessage中將回調作為第三項參數進行傳遞(我們將postMessage當中的參數數量視為變量,但希望能用更為一致的消息收發API來代替這種變量參數機制)。

• 其它。

我們在開發消息機制時犯下了一個嚴重的設計錯誤,即所有消息都會被同步發出。由于JavaScript采用單線程機制,大量有待執行的復雜消息操作會構成長隊列,而且很容易造成UI卡死。通過調整讓Milo擁有異步式消息發送機制并非難事(所有訂閱者利用setTimeout(subscriber, 0)接受其自身執行block的調用),變更框架及應用程序的其余部分則相對更難——雖然大多數消息都能夠以異步方式發送,但也有一些必須采用同步發送機制(主要是那些內部包含數據或者需要調用preventDefault的DOM事件)。在默認情況下,消息現在會以異步方式發送,但我們可以利用以下方式在消息發出時使其遵循同步機制:

  1. component.postMessageSync('mymessage', data); 

或者在創建訂閱時:

  1. component.onSync('mymessage'function(msg, data) {  
  2.     //...  
  3. }); 

我們作出的另一項設計決策是將消費機制方法顯示在使用它們的對象之上。最初,這些方法只是簡單被混雜在對象當中,但我們并不希望將所有方法都顯示出來、而且也不可能使用彼此獨立的消息機制。因此我們通過調整讓消息機制作為以Mixin抽象類為基礎的單獨類。

Mixin類允許我們將某個類的各項方法顯示在宿主對象之上,在這種情況下,當這些方法被調用時、其背景仍然是Mixin而非宿主對象。

事實證明這是一套非常便捷的處理機制——我們可以對需要顯示的方法進行全面控制,并根據需要變更其名稱。它還允許我們在同一對象上配備兩種消息機制,并將其用于model。

總體而言,Milo消息機制確實是一套穩定可靠的解決方案,而且足以在瀏覽器內以及Node.js中發揮作用。在構成我們這套產品內容管理系統的數萬行代碼中,它一直擁有相當出色的使用頻率。

下期預告

在下一篇文章中,我們將探討Milo項目最實用但可能也是最復雜的部分。Milo方案不僅允許用戶安全而且深入地進行屬性訪問,同時也能夠從任意層級對事件訂閱作出調整。

我們將一同探索實施記錄,了解我們如何利用connector對象實現數據源的單向或者雙向綁定。

原文鏈接:http://code.tutsplus.com/articles/rolling-your-own-framework--cms-21810

責任編輯:林師授 來源: 51CTO
相關推薦

2014-08-12 09:54:05

Android定制化啟動器

2017-05-08 14:27:49

PHP框架函數框架

2017-08-18 08:00:04

SaaS云計算公有云

2024-09-14 14:09:40

2021-12-23 06:07:21

前端技術編程

2019-01-31 13:43:48

Spring BootStarter開發

2021-05-26 10:21:31

Python音樂軟件包

2011-09-16 16:22:45

Android應用DXHome

2024-10-16 09:49:18

2021-01-18 05:30:22

串口通信Qt

2022-08-11 07:32:51

Starter自動裝配

2010-10-14 14:31:31

Ubuntu發行版

2018-01-29 20:12:11

python翻譯命令行

2022-11-30 07:49:49

交互事件屏幕手勢識別

2020-11-03 10:35:39

Python

2010-07-12 10:48:36

2023-04-12 07:35:33

2021-02-05 05:29:51

服務器GitGogs

2013-06-26 08:52:12

2022-04-18 19:02:53

chrome擴展瀏覽器
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 国产精品久久777777 | 久久久一区二区 | 亚洲视频在线播放 | 青青草免费在线视频 | 欧美在线视频一区 | 91就要激情 | 亚洲成av人片在线观看 | 99久久免费观看 | 99精品电影 | 99亚洲综合 | 亚洲国产精品一区 | 亚洲免费在线观看av | 亚洲免费人成在线视频观看 | 日韩欧美国产精品 | 国产第一页在线播放 | 欧美日韩电影一区二区 | 91亚洲国产精品 | 精品国产91乱码一区二区三区 | 日本aⅴ中文字幕 | 中文字幕中文字幕 | 一级黄a视频 | 精品成人一区二区 | 99久久久久| 一级片视频免费 | 亚洲欧美成人影院 | 国产中文字幕在线观看 | 另类亚洲视频 | 日韩视频国产 | 日韩精品在线视频 | 欧美中文一区 | 视频一区二区在线观看 | 无码日韩精品一区二区免费 | 在线看黄免费 | 成人精品一区亚洲午夜久久久 | 日韩欧美视频免费在线观看 | 一级免费毛片 | 狠狠狠色丁香婷婷综合久久五月 | 97av视频在线观看 | 亚洲人成在线播放 | 伊人色综合久久久天天蜜桃 | 精精国产xxxx视频在线播放 |