ZRender源码分析2:Storage(Model层)
回顧
上一篇請移步:zrender源碼分析1:總體結構
本篇進行ZRender的MVC結構中的M進行分析
總體理解
上篇說到,Storage負責MVC層中的Model,也就是模型,對于zrender來說,這個model就是shape對象,在1.x表現的還不強烈,到了2.x, 在zr.addShape()的時候,傳入的參數就必須是new出來的對象了詳情請看這里 2.x相比1.x的變化 ,關于這個變化多說點吧,那就是從1.x升級到2.x的時候,因為方式變了,總不能改掉所有的代碼,總不能像ext一樣, (從ExtJS3升級到ExtJS4是一個特別痛苦的過程),所以我們在原有的可視化程序中,加入了如下helper(該程序基于ExtJS5)
Ext.define('Nts.Utils.ChartHelper', {singleton: true,shapeMap: {},requireMap: {},/*** 通過shape的類型獲得shape的構造函數* 由于zrender的升級,所以導致該方法的出現,詳情* see:https://github.com/ecomfe/zrender/wiki/2.x%E7%9B%B8%E6%AF%941.x%E7%9A%84%E5%8F%98%E5%8C%96** @param shapeType shape類型* @returns {Constructor}*/getShapeTypeConstructor: function (shapeType) {// 由于zrender2.0的addShape時不能add對象,只能add一個初始化好的shape類,// 所以每次都需要require加載所需的類,在這里,shapeMap是一個緩存對象// 因為echarts包含了requirejs的源碼,但是沒有將define和require方法暴露出來// 迫不得已修改了echarts的源代碼,window.require = require;if (!this.shapeMap[shapeType]) {this.shapeMap[shapeType] = require('zrender/shape/' + Ext.String.capitalize(shapeType));}return this.shapeMap[shapeType];},/*** 根據shape類型和傳入shape的參數,新建shape類,返回的結果可以直接被addShape** 該方法有多個重載,如下** 1.Nts.Utils.ChartHelper.makeShapeInstance('image',{scale:[1,2],hover:....});* 2.Nts.Utils.ChartHelper.makeShapeInstance({shape:'image',scale:[1,2],hover:....});** 第2中方式為zrender1.x中兼容的方式,其中shape屬性可以是 shape|shapeType|type** @param shapeType shape類型* @param option 參數* @returns {Object} shape對象*/makeShapeInstance: function (shapeType, option) {if (Ext.isObject(shapeType)) {option = shapeType;shapeType = option.shape || option.shapeType || option.type}var ctor = this.getShapeTypeConstructor(shapeType);if (!ctor) new Error('cannot find this shape in zrender');return new ctor(option);} });這樣一來,就能夠繼續像之前一樣愉快的玩耍了。言歸正傳,把代碼全部折疊起來,我們來看看總體的結構。?
還好還好,這里的結構還是超級簡單。
- 1.這是個典型的JS創建對象的結構, var Storage = function () {}; Storage.prototype.add = function () {.....};
- 2.方法附加在protype上,屬性寫在構造函數里,每個附加到prototype的方法都返回this,支持鏈式調用
- 3.Storage n.貯存; 貯藏; 儲藏處,倉庫; 貯存器,蓄電(瓶); 維護所有的shape,可以通過其中的一些屬性進行查看
下面,咱們來逐個擊破。
構造函數
二話不說,先貼代碼
/*** 內容倉庫 (M)**/ function Storage() {// 所有常規形狀,id索引的mapthis._elements = {};// 所有形狀的z軸方向排列,提高遍歷性能,zElements[0]的形狀在zElements[1]形狀下方this._zElements = [];// 高亮層形狀,不穩定,動態增刪,數組位置也是z軸方向,靠前顯示在下方this._hoverElements = [];// 最大zlevelthis._maxZlevel = 0;// 有數據改變的zlevelthis._changedZlevel = {}; }作者都注釋了,這是個內容倉庫,又想想,這不就是相當于糧倉嘛,shape對象就是一個一個的糧食。構造函數里的_elements,_zElement,_hoverElements就是糧倉。 而_elements和_zElements這兩個變量其實存入的是一樣的東西,只是存入的方式不太相同而已。其中,zElement這個變量中的z,大概就是zlevel(分層)的意思, 我想這便是zrender的最核心的思想,分層繪圖。接下來咱們用一個取(bei)巧(bi)的方式,來看看內存中的呈現。打開zrender.js,加入一行代碼:window.z = this;
function ZRender(id, dom) {this.id = id;this.env = require('./tool/env');this.storage = new Storage();this.painter = new Painter(dom, this.storage);this.handler = new Handler(dom, this.storage, this.painter);window.z = this; // 把z透漏出去// 動畫控制this.animatingShapes = [];this.animation = new Animation({stage : {update : getAnimationUpdater(this)}});this.animation.start(); }然后,運行如下示例:
require(['../src/zrender','../src/shape/Image','../src/shape/Text','../src/shape/Circle'],function (zrender, ImageShape, TextShape, CircleShape) {var box = document.getElementById('box');var zr = zrender.init(box);zr.addShape(new CircleShape({style: {x: 120,y: 120,r: 50,color: 'red'},hoverable: true}));zr.addShape(new TextShape({style: {x: 220,y: 220,color: 'red',text: 'something text'},hoverable: true,zlevel: 2}));zr.render(); });最后,在控制臺中輸入z,回車,看到如下打印:?
可以很明顯的看到,_elements里的東西,是直接塞入的,不管什么順序,而zElements里的東西,是按照shape對象的zlevel進行存放的,具體怎么維護,就要看怎么增刪改查了
PS:這張圖比較重要,在下面增刪改查的時候,可以詳盡的表現出其過程
增
/*** 添加** @param {Shape} shape 參數*/ Storage.prototype.add = function (shape) {shape.updateNeedTransform();shape.style.__rect = null;this._elements[shape.id] = shape;this._zElements[shape.zlevel] = this._zElements[shape.zlevel] || [];this._zElements[shape.zlevel].push(shape);this._maxZlevel = Math.max(this._maxZlevel, shape.zlevel);this._changedZlevel[shape.zlevel] = true;/*** _elements ->* {* _zrender_101_: shapeObject,* _zrender_102_: shapeObject,* _zrender_103_: shapeObject,* ...* }** _zrender_103_ 為guid生成的** _zElements ->* {* 1: [shapeObject,shapeObject],* 2: [shapeObject,shapeObject....],* 3. [...]* }** 123 為層數** _maxZlevel: 3* changedZlevel: {1:true,2:true....}*/return this; }; /*** 添加高亮層數據** @param {Object} params 參數*/ Storage.prototype.addHover = function (params) {/*** 這里判斷了一大推參數,來預處理是否需要變形,變形金剛(Transformers)* 豆瓣電影:http://movie.douban.com/subject/7054604/* 在最初添加的時候,處理變形開關,就不用在用到的時候重新做了*/if ((params.rotation && Math.abs(params.rotation[0]) > 0.0001)|| (params.position&& (Math.abs(params.position[0]) > 0.0001|| Math.abs(params.position[1]) > 0.0001))|| (params.scale&& (Math.abs(params.scale[0] - 1) > 0.0001|| Math.abs(params.scale[1] - 1) > 0.0001))) {params.needTransform = true;}else {params.needTransform = false;}this._hoverElements.push(params); //簡單的將高亮層push到_hoverElements中return this; };- 1._elements是以id為key,shape對象為value,進行存儲
- 2._zElements是一個數組,以level為數組下標,同一個level的shape對象集合組成數組為值(如果該層沒有初始化,會有一個初始化的過程)
- 3.每次add,都會重置_maxZlevel變量,它始終表示最大的level;_changedZlevel是一個對象,表示變動的level(如果變動,在painter中會進行重繪)
- 4.addHover的時候,先預處理needTransform參數,之后,將shape對象直接塞入_hoverElements數組,不做復雜處理
刪
/*** 刪除高亮層數據*/ Storage.prototype.delHover = function () {this._hoverElements = [];return this; }; /*** 刪除,shapeId不指定則全清空** @param {string= | Array} idx 唯一標識*/ Storage.prototype.del = function (shapeId) {if (typeof shapeId != 'undefined') {var delMap = {};/*** 處理各種重載* 1.如果不是個數組,直接加入到delMap中* 2.如果是個數組,遍歷之*/if (!(shapeId instanceof Array)) {// 單個delMap[shapeId] = true;}else {// 批量刪除if (shapeId.lenth < 1) { // 空數組return;}for (var i = 0, l = shapeId.length; i < l; i++) {delMap[shapeId[i].id] = true;}}var newList;var oldList;var zlevel;var zChanged = {};for (var sId in delMap) {if (this._elements[sId]) {zlevel = this._elements[sId].zlevel;this._changedZlevel[zlevel] = true;/*** 這里主要處理zElements中元素的刪除* 這里確認每一個zlevel只遍歷一次,因為一旦進入這個if,在if的末尾,就會將flag設置為false,下次就進不來** 1.遍歷delMap,取出單個shape的zlevel,然后從_zElements[zlevel] 取出所有,命名為oldList* 2.遍歷oldList,如果delMap中沒有當前遍歷的shape,就加入到newList,最后該層的_zElements[zlevel]就是newList* 3.設置標志位,使之為false,表示該層已經被處理,就不要再次處理了*/if (!zChanged[zlevel]) {oldList = this._zElements[zlevel];newList = [];for (var i = 0, l = oldList.length; i < l; i++){if (!delMap[oldList[i].id]) {newList.push(oldList[i]);}}this._zElements[zlevel] = newList;zChanged[zlevel] = true;}//將shape從_elements中刪除delete this._elements[sId];}}}else{// 不指定shapeId清空this._elements = {};this._zElements = [];this._hoverElements = [];this._maxZlevel = 0; //最大zlevelthis._changedZlevel = { //有數據改變的zlevelall : true};}return this; };- 1.delHover方法很是簡單,將_hoverElements中的東西清空,返回this
- 2.關于del方法,如果不傳入shapeId,會將所有的shape都刪除,全部倉庫變量清空,all:true,就是表示所有層重繪
- 3.對參數的重載進行處理,如果是數組,遍歷之
- 4.shapeId instanceof 在某種情況下,會有問題的吧?為啥不用 Object.prototype.toString.call(xxx) === '[object Array]',為了可讀性?
- 5.對于_elements中的刪除,一句delete this._elements[sId];搞定,但是對于_zElements,就要費一番功夫了,具體移步代碼中的注釋吧
改
/*** 修改** @param {string} idx 唯一標識* @param {Object} params 參數*/ Storage.prototype.mod = function (shapeId, params) {var shape = this._elements[shapeId];if (shape) {shape.updateNeedTransform();shape.style.__rect = null;this._changedZlevel[shape.zlevel] = true; // 可能修改前后不在一層/*** 將參數合并,params && util.merge(shape, params, true);** this._changedZlevel[shape.zlevel] = true; 這里是為了防范:** var imageShape = new ImageShape({src:'xxx.png',zlevel:1});* imageShape.mod({zlevel:3});** 這里就是:level1和level3都變化了,_maxZlevel也變化了。*/if (params) {util.merge(shape, params, true);}this._changedZlevel[shape.zlevel] = true; // 可能修改前后不在一層this._maxZlevel = Math.max(this._maxZlevel, shape.zlevel);}return this; };- 1.updateNeedTransform這個方法,也是預處理變形金剛的問題
- 2.為了防止修改shape對象時不在同一層的問題,在前后都執行了this._changedZlevel[shape.zlevel] = true;雖然很羅嗦,但也很必要
- 3.util.merge的作用是將新加入的params合并到原來的參數中,具體代碼就不再羅嗦了
- 4.最后重置_maxZlevel,在z軸遍歷的時候,確保索引。
查
/*** 遍歷迭代器** @param {Function} fun 迭代回調函數,return true終止迭代* @param {Object=} option 迭代參數,缺省為僅降序遍歷常規形狀* hover : true 是否迭代高亮層數據* normal : 'down' | 'up' | 'free' 是否迭代常規數據,迭代時是否指定及z軸順序*/ Storage.prototype.iterShape = function (fun, option) {/*** 處理默認情況 option = option ||{ hover: false, normal: 'down'};*/if (!option) {option = {hover: false, //不遍歷高亮層normal: 'down' //高層優先};}if (option.hover) {//高亮層數據遍歷for (var i = 0, l = this._hoverElements.length; i < l; i++) {if (fun(this._hoverElements[i])) {return this;}}}var zlist;var len;if (typeof option.normal != 'undefined') {//z軸遍歷: 'down' | 'up' | 'free'switch (option.normal) {case 'down':// 降序遍歷,高層優先var l = this._zElements.length;while (l--) {zlist = this._zElements[l];if (zlist) {len = zlist.length;while (len--) {if (fun(zlist[len])) {return this;}}}}break;case 'up'://升序遍歷,底層優先for (var i = 0, l = this._zElements.length; i < l; i++) {zlist = this._zElements[i];if (zlist) {len = zlist.length;for (var k = 0; k < len; k++) {if (fun(zlist[k])) {return this;}}}}break;// case 'free':default://無序遍歷for (var i in this._elements) {if (fun(this._elements[i])) {return this;}}break;}}return this; }; /*** 根據指定的shapeId獲取相應的shape屬性** @param {string=} idx 唯一標識*/ Storage.prototype.get = function (shapeId) {return this._elements[shapeId]; }; Storage.prototype.getMaxZlevel = function () {return this._maxZlevel; };Storage.prototype.getChangedZlevel = function () {return this._changedZlevel; };Storage.prototype.clearChangedZlevel = function () {this._changedZlevel = {};return this; };Storage.prototype.setChangedZlevle = function (level) {this._changedZlevel[level] = true;return this; }; Storage.prototype.hasHoverShape = function () {return this._hoverElements.length > 0; };- 1.iterShape分為三種遍歷的方式(無序free,從上至下down,從下至上up),有一個開關(是否遍歷高亮層hover)
- 2.如果沒有指定option,設置默認值,不遍歷高亮層,從上至下遍歷
- 3.如果需要遍歷高亮層,遍歷_hoverElements數組,調用回調函數fun,如果fun的返回值能轉化為true,直接return掉了(多說一句,不知可否像jQuery的each一樣,是false的時候再return,就不用每次在函數末尾return false了?)
- 4.如果down和up的時候,遍歷的是_zElemements數組,因為層數可能是間隔的,所以每次取出,都會判斷一下是否為undefined,如果有值,遍歷里面的數組,執行fun回調,return的邏輯跟上一條一樣。
- 5.如果是無序遍歷,最好辦,遍歷_elements數組,進行調用fun
- 6.至于get(通過id獲取shape對象)/getMaxZlevel(獲取最大層級)/getChangedZlevel(獲取改變的層級對象)/clearChangedZlevel(清空層級變化)/setChangedZlevle(設置某個層級變化為true)/hasHoverShape(是否存在高亮層)都比較簡單,就不詳述了
總結
- 1.其實這個Storage很好理解,主要是對Shape對象進行一些增刪改查的封裝(封裝的好處我就不說了,自行腦補吧)
- 2.可見作者很是理解我們這些新手,代碼寫的相當易懂,我喜歡(恨死了jQuery了),自行猜測,不要噴我哦
- 3.還有一個drift漂移的方法沒有提到,以后再說吧
轉載于:https://www.cnblogs.com/hhstuhacker/p/zrender-source-storage-advance.html
總結
以上是生活随笔為你收集整理的ZRender源码分析2:Storage(Model层)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2014/9/12 play with
- 下一篇: Visual C++ 基础数据类型的转换