回顾

上一篇请移步: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索引的map
this._elements = {}; // 所有形状的z轴方向排列,提高遍历性能,zElements[0]的形状在zElements[1]形状下方
this._zElements = []; // 高亮层形状,不稳定,动态增删,数组位置也是z轴方向,靠前显示在下方
this._hoverElements = []; // 最大zlevel
this._maxZlevel = 0; // 有数据改变的zlevel
this._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; //最大zlevel
this._changedZlevel = { //有数据改变的zlevel
all : 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漂移的方法没有提到,以后再说吧

ZRender源码分析2:Storage(Model层)的更多相关文章

  1. ZRender源码分析3:Painter(View层)-上

    回顾 上一篇说到:ZRender源码分析2:Storage(Model层),这次咱看来看看Painter-View层 总体理解 Painter这个类主要负责MVC中的V(View)层,负责将Stora ...

  2. ZRender源码分析4:Painter(View层)-中

    回顾 上一篇说到:ZRender源码分析3:Painter(View层)-上,接上篇,开始Shape对象 总体理解 先回到上次的Painter的render方法 /** * 首次绘图,创建各种dom和 ...

  3. zrender源码分析3--初始化Painter绘图模块

    接上次分析到初始化ZRender的源码,这次关注绘图模块Painter的初始化 入口1:new Painter(dom, this.storage); // zrender.js /** * ZRen ...

  4. ZRender源码分析5:Shape绘图详解

    回顾 上一篇说到:ZRender源码分析4:Painter(View层)-中,这次,来补充一下具体的shape 关于热区的边框 以圆形为例: document.addEventListener('DO ...

  5. Spark源码分析之-Storage模块

    原文链接:http://jerryshao.me/architecture/2013/10/08/spark-storage-module-analysis/ Background 前段时间琐事颇多, ...

  6. zrender源码分析2--初始化Storage

    接上次分析到初始化ZRender的源码,这次关注内容仓库Storage的初始化 入口1:new Storage(); // zrender.js /** * ZRender接口类,对外可用的所有接口都 ...

  7. zrender源码分析1:总体结构

    开始 zrender(Zlevel Render) 是一个轻量级的Canvas类库,这里是GitHub的网址 点我, 类似的类库有Kinetic.JS.EaselJS. 但貌似都没有zrender好用 ...

  8. MyBatis源码分析之核心处理层

    目录 1 传统方式源码剖析 1.1 初始化流程 1.2 执行SQL流程 1.2.1 获取SqlSession 1.2.2 执行SqlSession接口 1.2.3 执行Executor接口 1.2.4 ...

  9. zrender源码分析--初探如何画一个圆

    今天是想看看使用zrender框架如何去,画一个圆,再加“circle”的文字在圆心. 然后开始代码: 如何部署代码,让zrender跑起来这边就不说了,官方例子就有写,地址是:https://git ...

随机推荐

  1. MySQL 初学笔记 ① -- MySQL用户登录权限控制

    1. MySQL 登录 MySQL -u username -p 2. MySQL 创建用户 use mysql //进入mysql 表 INSERT INTO user (Host,User,Pas ...

  2. 轻松实现HTML5时钟(分享下自己对canvas的理解,原来没你想像的那么难哦)

    Hey,guys! 让我们一起用HTML5实现一下简易时钟吧! 接触canvas时, 我突然有一种非常熟悉的感觉------canvas的部分的功能其实和Photoshop中的 钢笔工具 是一样的.所 ...

  3. python运维开发(十六)----Dom&&jQuery

    内容目录: Dom 查找 操作 事件 jQuery 查找 筛选 操作 事件 扩展 Dom 文档对象模型(Document Object Model,DOM)是一种用于HTML和XML文档的编程接口.它 ...

  4. Roads in the North(POJ 2631 DFS)

    Description Building and maintaining roads among communities in the far North is an expensive busine ...

  5. A Distributed Multichannel MAC Protocol for Multihop Cognitive Radio Networks

    2010 这个呢,就是time slotted的DSA网络MAC层协议. 跟上一篇单纯的Multi Channel实现类似,不过这里是CR网络,因为多了嗅探等操作. 简单的说,time slotted ...

  6. javascript prototype __proto__区别

    An Object's __proto__ property references the same object as its internal [[Prototype]] (often refer ...

  7. linux系统下,11款常见远程桌面控制软件

    linux系统下,11款常见远程桌面控制软件 一. Grdc 它是一个用GTK+编写的,适用于gnome桌面环境的远程桌面访问软件.看图: 常见功能: 1.提供全屏,窗口化的远程控制.支持高分辨率下的 ...

  8. UESTC_Little Deer and Blue Cat CDOJ 1025

    In DOTA, there are two Intellegence heroes. One is Enchantress, who is usually called Little Deer by ...

  9. Libgdx开发ios游戏

    今天亲自尝试了LibGDX如何开发ios游戏, 必须条件: 1:mac操作系统,mac下必须安装Xcode     好像ios开发必须在mac操作系统下 2:mac下安装eclipse 3:eclip ...

  10. iOS 苹果app提交 ITC.apps.validation.prerelease_build_missing

    提示这信息,由于没有选择包文件, 在提交页面以下bulid的区域, 加入你的app就可以 watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvYWxpbmNleG ...