您的位置:首頁>正文

jsPlumb之流程圖專案總結及實例

在使用jsPlumb過程中,所遇到的問題,以及解決方案,文中引用了《資料結構與演算法JavaScript描述》的相關圖片和一部分代碼.截圖是有點多,有時比較懶,沒有太多的時間去詳細的編輯.

前言

首先是UML類圖

然後是流程圖

使用了jsPlumb的相關功能,初版是可以看到雛形了,差不多用了兩個月的時間,中間斷斷續續的又有其它工作穿插,但還是把基本功能做出來了.

其實做完了之後,才發現jsPlumb的功能,只用到了很少的一部分,更多的是對於內部資料結構的理解和實現,只能說做到了資料同步更新,距離資料驅動仍然有一定的距離.

這裡會總結和記錄一下專案中遇到的問題,和解決的方法,如果有更好的方法,歡迎指出.

對於連線上的多個標籤的處理

如上圖所示,一開始是認為是否是要在連線時,配置兩個overlays,

var j = jsPlumb.getInstance; j.connect({ source:source, target:target, overlays:[ "Arrow", ["label",{label:"foo1",location:0.25,id:"m1"}], ["label",{label:"foo2",location:0.75,id:"m2"}] ] })

當然,這裡也有坑,如果id重複,那麼會使用最後一個,而不會重合,包括jsPlumb內部緩存的資料都只會剩下最後的那個.

後面發現,其實也可以通過importDefaults函數來動態修改配置項.

j.importDefaults({ ConnectionOverlays: [ ["Arrow", { location: 1, id: "arrow", length: 10, foldback: 0, width: 10 }], ["Label", { label: "n", id: "label-n", location: 0.25, cssClass: "jspl-label" }], ["Label", { label: "1", id: "label-1", location: 0.75, cssClass: "jspl-label" }] ] })

只不過這樣,只會在運行了函數之後的連線裡,才能有兩個標籤顯示,而之前的則無法一起變化. 所以為了方便,直接在初始化裡將其給修改了.

Groups的使用

在做流程圖時,Group確實是個問題,如上圖的無限嵌套層級中,就無法使用jsPlumb提供的Groups功能.

按照文檔中來說,如果標識一個元素為組,則該組中的元素則會跟隨組的移動而移動,連線也是,但問題就是一旦一個元素成為組了,那就不能接受其它組元素了,換句話說,它所提供的的Groups方法只有一層,自然無法滿足要求.

先把總結的組的用法貼出來:

j.addGroup({ el:el, id:"one" constrain:true, // 子元素僅限在元素內拖動 droppable:true, // 子元素是否可以放置其他元素 draggable:true, // 默認為true,組是否可以拖動 dropOverride:true ,// 組中的元素是否可以拓展到其他組,為true時表示否,這裡的拓展會對dom結構進行修改,而非單純的位置移動 ghost:true, // 是否創建一個子元素的副本元素 revert:true, // 元素是否可以拖到只有邊框可以重合 })

後面採用了新的方式,在節點移動時,動態刷新連線

j.repaintEverything;

而為了不阻塞頁面,需要用到函數節流throttle

function throttle(fn,interval){ var canRun = true; return function{ if(!canRun) return; canRun = false; setTimeout(function{ fn.apply(this,arguments); canRun = true; },interval ? interval : 300); }; };

這是一個簡單的實現方式,主要就是為了減少dom中事件移動時重複調用的事件,同時達到執行事件的目的(只允許一個函數在x毫秒內執行一次);

當然,也可以使用underscore.js中自帶的_.throttle函數,同樣可以達到目的.

這裡的html結構就使用了嵌套的層級,將父級和子級使用這種層級保存到內部的資料來源裡

多層or一層 資料結構解析

類似這種實際存在嵌套關係的資料體,有兩種方式可以進行管理,

多層級嵌套:類似

[ { id:"1", child:{ id:"2", child:{ id:"3", child:{} } } } ]

用來進行管理的話,優點是直觀,能根據層級就知道整體結構大概是多少,轉換成xml或者html也很方便. 但缺點就是進行查找和修改,並不是那麼方便.

一層展示所有節點:類似

[ { id:"1", child:[{ id:"2" }] }, { id:"2", parentId:"1", child:[{ id:"3" }] }, { id:"3", parentId:"2", child: } ]

這種結構好處就是全部在一個層級中,查找起來和修改資料非常方便,而如果想要解析成多層級的結構,只需要運用遞迴,來生成新結構:

function mt{ var OBJ; this.root = null; this.Node = function(e) { this.id = e.id; this.name = e.name; this.parentId = e.parentId; this.children = ; }; this.insert=function(e,key){ function add(obj,e){ if(obj.id == e.parentId){ obj.children.push(e); } else { for (var i = 0; i < obj.children.length;="" i++)="" {="" add(obj.children[i],="" e);="" }="" }="" }="" if="" (e="" !="undefined)" {="" e="new" this.node(e);="" }="" else="" {="" return;="" }="" if="" (this.root="=" null)="" {="" this.root="e;" }="" else="" {="" obj="this.root;" add(obj,="" e);="" }="" }="" this.init="function(data){" var="" _this="this;" for(var="" i="">

將一層的陣列通過初始化函數init,就可以轉為多層級

如果想轉成html結構,只需要稍微改下函數,就可以實現了.

校驗流程是否存在死路(是否存在不能到達圖的終點的路徑的點)

這個就完全得靠演算法來實現了.首先,對於圖的理解是重點

我也懶得打字了,直接用圖表示一下,基本的圖大致是這樣,而具體的表現形式則是

可以看到,基礎的圖的表現形式,可以用一個鄰接表來表示;

而實現,則可以看到下列的代碼:

function Graph1(v) { this.vertices = v; // 總頂點 this.edges = 0; // 圖的邊數 this.adj = ; // 通過 for 迴圈為陣列中的每個元素添加一個子陣列來存儲所有的相鄰頂點,[並將所有元素初始化為空字串。]? for (var i = 0; i "; for (var j = 0; j < this.vertices;="" ++j)="" {="" if="" (this.adj[i][j]="" !="undefined)" {="" str="" +="this.adj[i][j]" +="" '="" '="" }="" }="" console.log("表現形式為:"="" +="" str);="" }="" console.log(this.adj);="" }="">

而光構建是不夠的,所以來看下基礎的搜索方法: 深度優先搜索和廣度優先搜索;

深度優先搜索

先從初始節點開始訪問,並標記為已訪問過的狀態,再遞迴的去訪問在初始節點的鄰接表中其他沒有訪問過的節點,依次之後,就能訪問過所有的節點了

/** * 深度優先搜索演算法 * 這裡不需要頂點,也就是鄰接表的初始點 */ this.dfs = (v) { this.marked[v] = true; for (var w of this.adj[v]) { if (!this.marked[w]) { this.dfs(w); } } }

根據圖片和上述的代碼,可以看出深度搜索其實可以做很多其他的擴展

廣度優先搜索 /** * 廣度優先搜索演算法 * @param {[type]} s [description] */ this.bfs = function(s) { var queue = ; this.marked[s] = true; queue.push(s); // 添加到隊尾 while (queue.length > 0) { var v = queue.shift; // 從隊首移除 console.log("Visisted vertex: " + v); for (var w of this.adj[v]) { if (!this.marked[w]) { this.edgeTo[w] = v; this.marked[w] = true; queue.push(w); } } } }

而如果看了《資料結構與演算法JavaScript描述》這本書,有興趣的可以去實現下查找最短路徑和拓撲排序;

兩點之間所有路徑

這算是找到的比較能理解的方式來計算

以上圖為例,這是一個簡單的流程圖,可以很簡單的看出,右邊的流程實際上是未完成的,因為無法到達終點,所以是一個非法點,而通過上面的深度搜索,可以看出,只要對深度優先搜索演算法進行一定的修改,那麼就可以找到從開始到結束的所有的路徑,再通過對比,就可以知道哪些點無法到達終點,從而確定非法點. 上代碼:

/** * 深度搜索,dfs,解兩點之間所有路徑 * @param {[type]} v [description] * @return {[type]} [description] */ function Graph2(v) { var _this = this; this.vertices = v; // 總頂點 this.edges = 0; //圖的起始邊數 this.adj = ; //內部鄰接表表現形式 this.marked = ; // 內部頂點訪問狀態,與鄰接表對應 this.path = ; // 路徑表示 this.lines = ; // 所有路徑匯總 for (var i = 0; i < this.vertices;="" ++i)="" {="" _this.adj[i]=";" }="" *="" *="" 初始化訪問狀態="" *="" @return="" {[type]}="" [description]="" */="" this.initmarked="function" {="" for="" (var="" i="0;" i="">< _this.vertices;="" ++i)="" {="" _this.marked[i]="false;" }="" };="" *="" *="" 在鄰接表中增加節點="" *="" @param="" {[type]}="" v="" [description]="" *="" @param="" {[type]}="" w="" [description]="" */="" this.addedge="function(v," w)="" {="" this.adj[v].push(w);="" this.edges++;="" };="" *="" *="" 返回生成的鄰接表="" *="" @return="" {[type]}="" [description]="" */="" this.showgraph="function" {="" return="" this.adj;="" };="" *="" *="" 深度搜索演算法="" *="" @param="" {[type]}="" v="" [起點]="" *="" @param="" {[type]}="" d="" [終點]="" *="" @param="" {[type]}="" path="" [路徑]="" *="" @return="" {[type]}="" [description]="" */="" this.dfs="function(v," d,="" path)="" {="" var="" _this="this;" this.marked[v]="true;" path.push(v);="" if="" (v="=" d)="" {="" var="" arr=";" for="" (var="" i="0;" i="">< path.length;="" i++)="" {="" arr.push(path[i]);="" }="" _this.lines.push(arr);="" }="" else="" {="" for="" (var="" w="" of="" this.adj[v])="" {="" if="" (!this.marked[w])="" {="" this.dfs(w,="" d,="" path);="" }="" }="" }="" path.pop;="" this.marked[v]="false;" };="" this.verify="function(arr," start,="" end)="" {="" this.initmarked;="" for="" (var="" i="0;" i="">< arr.length;="" i++)="" {="" _this.addedge(arr[i].from,="" arr[i].to);="" }="" this.dfs(start,="" end,="" this.path);="" return="" this.lines;="" };="">

可以看出修改了addEdge函數,將鄰接表中的雙向記錄改為單向記錄,可以有效避免下圖的錯誤計算:

只計算起點到終點的所有連線有時並不客觀,如果出現

這種情況的話,實際上深度遍歷並不能計算出最右邊的節點是合法的,那麼就需要重新修改起點和終點,來推導是否能夠到達終點.從而判定該點是否合法.至於其他的,只是多了個返回值,存儲了一下計算出來的所有路徑.

而在dfs函數中,當滿足能夠從起點走到終點的,則記錄下當前的path中的值,保存到lines中去,而每一次對於path的推入或者推出,保證了只有滿足條件的點,才能被返回;

而this.marked[v] = false,則確保了,在每一次重新計算路徑時,都會驗證每個點是否存在不同的相對於終點能夠到達的路徑是否存在.

當然,一定會有更加簡單的方法,我這裡只是稍微修改了下基礎的代碼!

redo和undo

這是我覺得最簡單卻耗時最久的功能,思路都知道:創建一個佇列,記錄每一次創建一個流程節點,刪除一個流程節點,建立一個新的關聯關係,刪除一個新的關聯關係等,都需要記錄下來,再通過統一的介面來訪問佇列,執行操作.

但在具體實現上,jsPlumb的remove確實需要注意一下:

首先,如果需要刪除連線,那麼使用jsPlumb提供的detach方法,就可以刪除連線,注意,傳入的資料應該是connection物件.

當然,也可以使用remove方法,參數為選擇器或者element物件都可以,這個方法刪除的是一個節點,包括節點上所有的線.

而jsPlumb中會內部緩存所有的資料,用於刷新,和重連.

那麼當我移除一個多層級且內部有連線的情況時,如果只刪除最外層的元素,那麼內部的連線實際上並沒有清除,所以當redo或者移動時,會出現連線的端點有一端會跑到座標原點,也就是div上(0,0)的地方去.所以清除時,需要注意,要把內部的所有節點依次清除,才不會發生一些莫名其妙的bug.

而在刪除和連接連線上,我使用了jsPlumb提供的事件bind('connection')和bind("connectionDetached"),用於判斷一條連線被連接或者刪除.而在記錄這裡的redo和undo事件時,尤其要注意,需要首先確定刪除和連接時的連線的類型,否則會產生額外的佇列事件.

因此,在使用連接事件時,就可以使用

jsPlumb.connect({ source:"foo", target:"bar", parameters:{ "p1":34, "p2":new Date, "p3":function { console.log("i am p3"); } } });

來進行類型的傳參,這樣事件觸發時就可以分類處理.

也可以使用connection.setData事件,參數可以指定任意的值,通過connection.getData方法,就可以拿到相應的資料了.

而redo和undo本身確實沒有什麼東西

var defaults = { 'name': "mutation", 'afterAddServe':$.noop, 'afterUndo':$.noop, 'afterRedo':$.noop } var mutation = function(options){ this.options = $.extend(true,{},defaults,options); this.list = ; this.index = 0; }; mutation.prototype = { addServe:function(undo,redo){ if(!_.isFunction(undo) || !_.isFunction(redo)) return false; // 說明是在有後續操作時,更新了佇列 if(this.canRedo){ this.splice(this.index+1); }; this.list.push({ undo:undo, redo:redo }); console.log(this.list); this.index = this.list.length - 1; _.isFunction(this.options.afterAddServe) && this.options.afterAddServe(this.canUndo,this.canRedo); }, /** * 相當於保存之後清空之前的所有保存的操作 * @return {[type]} [description] */ reset:function{ this.list = ; this.index = 0; }, /** * 當破壞原來佇列時,需要對佇列進行修改, * index開始的所有存儲值都沒有用了 * @param {[type]} index [description] * @return {[type]} [description] */ splice:function(index){ this.list.splice(index); }, /** * 撤銷操作 * @return {[type]} [description] */ undo:function{ if(this.canUndo){ this.list[this.index].undo; this.index--; _.isFunction(this.options.afterUndo) && this.options.afterUndo(this.canUndo,this.canRedo); } }, /** * 重做操作 * @return {[type]} [description] */ redo:function{ if(this.canRedo){ this.index++; this.list[this.index].redo; _.isFunction(this.options.afterRedo) && this.options.afterRedo(this.canUndo,this.canRedo); } }, canUndo:function{ return this.index !== -1; }, canRedo:function{ return this.list.length - 1 !== this.index; } } return mutation;

每次在使用redo或者undo時,只需要判斷當前是否是佇列的尾端或者起始端,再確定是否redo或者undo就可以了.

調用時的undo和redo通過傳參,將不同的函數封裝進佇列裡,就可以減少耦合度.

放大縮小

這裡想了想還是記錄一下,方法採用了最簡單的mousedown和mousemove,讓元素在節流中動態的變化大小,就可以了,

只需要用一個節點,在點擊元素時,根據元素的大小來確定該輔助節點四個點的位置,就可以了,只要監聽了這四個點的位置,再同步給該定位元素,就能實現這一效果,方法就不貼了,沒有太多東西

小結

這次的項目我個人還是覺得蠻有意思的,可以學習新的演算法,瞭解新的資料結構,包括設計模式,也代入了其中,進行代碼的整合,所用到的中介軟體模式和發佈訂閱者模式都讓我對於js有了一個新的理解.雖然已經用require來管理模組,但結構仍然存在高度耦合的情況,應該還是被限制住了. 作為離職前的最後一次的專案來說,其實我感覺我的代碼能力仍然與年初沒有什麼太大的改變,也許是時候脫離安逸的環境,重新開始了.

如果想轉成html結構,只需要稍微改下函數,就可以實現了.

校驗流程是否存在死路(是否存在不能到達圖的終點的路徑的點)

這個就完全得靠演算法來實現了.首先,對於圖的理解是重點

我也懶得打字了,直接用圖表示一下,基本的圖大致是這樣,而具體的表現形式則是

可以看到,基礎的圖的表現形式,可以用一個鄰接表來表示;

而實現,則可以看到下列的代碼:

function Graph1(v) { this.vertices = v; // 總頂點 this.edges = 0; // 圖的邊數 this.adj = ; // 通過 for 迴圈為陣列中的每個元素添加一個子陣列來存儲所有的相鄰頂點,[並將所有元素初始化為空字串。]? for (var i = 0; i "; for (var j = 0; j < this.vertices;="" ++j)="" {="" if="" (this.adj[i][j]="" !="undefined)" {="" str="" +="this.adj[i][j]" +="" '="" '="" }="" }="" console.log("表現形式為:"="" +="" str);="" }="" console.log(this.adj);="" }="">

而光構建是不夠的,所以來看下基礎的搜索方法: 深度優先搜索和廣度優先搜索;

深度優先搜索

先從初始節點開始訪問,並標記為已訪問過的狀態,再遞迴的去訪問在初始節點的鄰接表中其他沒有訪問過的節點,依次之後,就能訪問過所有的節點了

/** * 深度優先搜索演算法 * 這裡不需要頂點,也就是鄰接表的初始點 */ this.dfs = (v) { this.marked[v] = true; for (var w of this.adj[v]) { if (!this.marked[w]) { this.dfs(w); } } }

根據圖片和上述的代碼,可以看出深度搜索其實可以做很多其他的擴展

廣度優先搜索 /** * 廣度優先搜索演算法 * @param {[type]} s [description] */ this.bfs = function(s) { var queue = ; this.marked[s] = true; queue.push(s); // 添加到隊尾 while (queue.length > 0) { var v = queue.shift; // 從隊首移除 console.log("Visisted vertex: " + v); for (var w of this.adj[v]) { if (!this.marked[w]) { this.edgeTo[w] = v; this.marked[w] = true; queue.push(w); } } } }

而如果看了《資料結構與演算法JavaScript描述》這本書,有興趣的可以去實現下查找最短路徑和拓撲排序;

兩點之間所有路徑

這算是找到的比較能理解的方式來計算

以上圖為例,這是一個簡單的流程圖,可以很簡單的看出,右邊的流程實際上是未完成的,因為無法到達終點,所以是一個非法點,而通過上面的深度搜索,可以看出,只要對深度優先搜索演算法進行一定的修改,那麼就可以找到從開始到結束的所有的路徑,再通過對比,就可以知道哪些點無法到達終點,從而確定非法點. 上代碼:

/** * 深度搜索,dfs,解兩點之間所有路徑 * @param {[type]} v [description] * @return {[type]} [description] */ function Graph2(v) { var _this = this; this.vertices = v; // 總頂點 this.edges = 0; //圖的起始邊數 this.adj = ; //內部鄰接表表現形式 this.marked = ; // 內部頂點訪問狀態,與鄰接表對應 this.path = ; // 路徑表示 this.lines = ; // 所有路徑匯總 for (var i = 0; i < this.vertices;="" ++i)="" {="" _this.adj[i]=";" }="" *="" *="" 初始化訪問狀態="" *="" @return="" {[type]}="" [description]="" */="" this.initmarked="function" {="" for="" (var="" i="0;" i="">< _this.vertices;="" ++i)="" {="" _this.marked[i]="false;" }="" };="" *="" *="" 在鄰接表中增加節點="" *="" @param="" {[type]}="" v="" [description]="" *="" @param="" {[type]}="" w="" [description]="" */="" this.addedge="function(v," w)="" {="" this.adj[v].push(w);="" this.edges++;="" };="" *="" *="" 返回生成的鄰接表="" *="" @return="" {[type]}="" [description]="" */="" this.showgraph="function" {="" return="" this.adj;="" };="" *="" *="" 深度搜索演算法="" *="" @param="" {[type]}="" v="" [起點]="" *="" @param="" {[type]}="" d="" [終點]="" *="" @param="" {[type]}="" path="" [路徑]="" *="" @return="" {[type]}="" [description]="" */="" this.dfs="function(v," d,="" path)="" {="" var="" _this="this;" this.marked[v]="true;" path.push(v);="" if="" (v="=" d)="" {="" var="" arr=";" for="" (var="" i="0;" i="">< path.length;="" i++)="" {="" arr.push(path[i]);="" }="" _this.lines.push(arr);="" }="" else="" {="" for="" (var="" w="" of="" this.adj[v])="" {="" if="" (!this.marked[w])="" {="" this.dfs(w,="" d,="" path);="" }="" }="" }="" path.pop;="" this.marked[v]="false;" };="" this.verify="function(arr," start,="" end)="" {="" this.initmarked;="" for="" (var="" i="0;" i="">< arr.length;="" i++)="" {="" _this.addedge(arr[i].from,="" arr[i].to);="" }="" this.dfs(start,="" end,="" this.path);="" return="" this.lines;="" };="">

可以看出修改了addEdge函數,將鄰接表中的雙向記錄改為單向記錄,可以有效避免下圖的錯誤計算:

只計算起點到終點的所有連線有時並不客觀,如果出現

這種情況的話,實際上深度遍歷並不能計算出最右邊的節點是合法的,那麼就需要重新修改起點和終點,來推導是否能夠到達終點.從而判定該點是否合法.至於其他的,只是多了個返回值,存儲了一下計算出來的所有路徑.

而在dfs函數中,當滿足能夠從起點走到終點的,則記錄下當前的path中的值,保存到lines中去,而每一次對於path的推入或者推出,保證了只有滿足條件的點,才能被返回;

而this.marked[v] = false,則確保了,在每一次重新計算路徑時,都會驗證每個點是否存在不同的相對於終點能夠到達的路徑是否存在.

當然,一定會有更加簡單的方法,我這裡只是稍微修改了下基礎的代碼!

redo和undo

這是我覺得最簡單卻耗時最久的功能,思路都知道:創建一個佇列,記錄每一次創建一個流程節點,刪除一個流程節點,建立一個新的關聯關係,刪除一個新的關聯關係等,都需要記錄下來,再通過統一的介面來訪問佇列,執行操作.

但在具體實現上,jsPlumb的remove確實需要注意一下:

首先,如果需要刪除連線,那麼使用jsPlumb提供的detach方法,就可以刪除連線,注意,傳入的資料應該是connection物件.

當然,也可以使用remove方法,參數為選擇器或者element物件都可以,這個方法刪除的是一個節點,包括節點上所有的線.

而jsPlumb中會內部緩存所有的資料,用於刷新,和重連.

那麼當我移除一個多層級且內部有連線的情況時,如果只刪除最外層的元素,那麼內部的連線實際上並沒有清除,所以當redo或者移動時,會出現連線的端點有一端會跑到座標原點,也就是div上(0,0)的地方去.所以清除時,需要注意,要把內部的所有節點依次清除,才不會發生一些莫名其妙的bug.

而在刪除和連接連線上,我使用了jsPlumb提供的事件bind('connection')和bind("connectionDetached"),用於判斷一條連線被連接或者刪除.而在記錄這裡的redo和undo事件時,尤其要注意,需要首先確定刪除和連接時的連線的類型,否則會產生額外的佇列事件.

因此,在使用連接事件時,就可以使用

jsPlumb.connect({ source:"foo", target:"bar", parameters:{ "p1":34, "p2":new Date, "p3":function { console.log("i am p3"); } } });

來進行類型的傳參,這樣事件觸發時就可以分類處理.

也可以使用connection.setData事件,參數可以指定任意的值,通過connection.getData方法,就可以拿到相應的資料了.

而redo和undo本身確實沒有什麼東西

var defaults = { 'name': "mutation", 'afterAddServe':$.noop, 'afterUndo':$.noop, 'afterRedo':$.noop } var mutation = function(options){ this.options = $.extend(true,{},defaults,options); this.list = ; this.index = 0; }; mutation.prototype = { addServe:function(undo,redo){ if(!_.isFunction(undo) || !_.isFunction(redo)) return false; // 說明是在有後續操作時,更新了佇列 if(this.canRedo){ this.splice(this.index+1); }; this.list.push({ undo:undo, redo:redo }); console.log(this.list); this.index = this.list.length - 1; _.isFunction(this.options.afterAddServe) && this.options.afterAddServe(this.canUndo,this.canRedo); }, /** * 相當於保存之後清空之前的所有保存的操作 * @return {[type]} [description] */ reset:function{ this.list = ; this.index = 0; }, /** * 當破壞原來佇列時,需要對佇列進行修改, * index開始的所有存儲值都沒有用了 * @param {[type]} index [description] * @return {[type]} [description] */ splice:function(index){ this.list.splice(index); }, /** * 撤銷操作 * @return {[type]} [description] */ undo:function{ if(this.canUndo){ this.list[this.index].undo; this.index--; _.isFunction(this.options.afterUndo) && this.options.afterUndo(this.canUndo,this.canRedo); } }, /** * 重做操作 * @return {[type]} [description] */ redo:function{ if(this.canRedo){ this.index++; this.list[this.index].redo; _.isFunction(this.options.afterRedo) && this.options.afterRedo(this.canUndo,this.canRedo); } }, canUndo:function{ return this.index !== -1; }, canRedo:function{ return this.list.length - 1 !== this.index; } } return mutation;

每次在使用redo或者undo時,只需要判斷當前是否是佇列的尾端或者起始端,再確定是否redo或者undo就可以了.

調用時的undo和redo通過傳參,將不同的函數封裝進佇列裡,就可以減少耦合度.

放大縮小

這裡想了想還是記錄一下,方法採用了最簡單的mousedown和mousemove,讓元素在節流中動態的變化大小,就可以了,

只需要用一個節點,在點擊元素時,根據元素的大小來確定該輔助節點四個點的位置,就可以了,只要監聽了這四個點的位置,再同步給該定位元素,就能實現這一效果,方法就不貼了,沒有太多東西

小結

這次的項目我個人還是覺得蠻有意思的,可以學習新的演算法,瞭解新的資料結構,包括設計模式,也代入了其中,進行代碼的整合,所用到的中介軟體模式和發佈訂閱者模式都讓我對於js有了一個新的理解.雖然已經用require來管理模組,但結構仍然存在高度耦合的情況,應該還是被限制住了. 作為離職前的最後一次的專案來說,其實我感覺我的代碼能力仍然與年初沒有什麼太大的改變,也許是時候脫離安逸的環境,重新開始了.

同類文章
Next Article
喜欢就按个赞吧!!!
点击关闭提示