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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/575255.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java语言概述、java语言特性、java语言发展史、java语言作用

Java介绍&#xff1a; Java语言概述&#xff1a; Java语言是由美国Sun&#xff08;Stanford University Network&#xff09;斯坦福网络公司的java语言之父–詹姆斯高斯林&#xff0c;在1995年推出的高级的编程语言。所谓编程语言&#xff0c;是计算机的语言&#xff0c;人们…

电脑显示器不亮主机正常_电脑主机已开机 显示屏却不亮(看完秒懂)

台式电脑是工作的得力助手&#xff0c;我们用的是软件&#xff0c;但是如果硬件出问题了&#xff0c;就无法工作了。有时会遇到台式电脑开机&#xff0c;主机开了&#xff0c;但是屏幕却不亮&#xff0c;是怎么回事呢&#xff1f;现在&#xff0c;笔者告诉大家怎样一步步查明原…

二进制的认识、进制之间的转换、计算机储存单位

二进制&#xff1a; 计算机中的数据不同于人们生活中的数据&#xff0c;人们生活采用十进制数&#xff0c;而计算机中全部采用二进制数表示&#xff0c;它只包含0、1两个数&#xff0c;逢二进一&#xff0c;如&#xff1a;1110。每一个0或者每一个1&#xff0c;叫做一个bit&am…

.net一个函数要用另一个函数的值_VLOOKUP函数

两个表格顺序不同&#xff0c;如何匹配合并&#xff1f;如下图&#xff1a;常常遇到有人问我&#xff0c;两个表格需要按照人名合并起来&#xff0c;但是两表的人名顺序并不相同&#xff0c;怎么处理呢&#xff1f;这个就要用到EXCEL大名鼎鼎的VLOOKUP函数了。具体语法如下&…

外部函数获取内部函数变量_一维随机变量的分布函数

一、分布函数、概率密度(一)一维随机变量的分布函数(二)一维离散型随机变量的概率分布(三)一维连续型随机变量的概率密度(四)一维连续型随机变量的函数的概率密度如&#xff1a;设X的概率密度为f(x), g(x)为连续函数&#xff0c;求Yg(x)的概率密度。方法一、定义法step 1. 求出…

DOS命令、Java语言开发环境(JVM、JDK、JRE)

DOS命令&#xff1a; DOS是一个早期的操作系统&#xff08;黑窗口&#xff09;&#xff0c;现在已经被Windows系统&#xff08;图形化&#xff09;取代&#xff0c;对于我们开发人员&#xff0c;目前需要在DOS中完成一些事情&#xff0c;Java语言的初学者&#xff0c;学习一些D…

shell执行docker命令卡挂住_Docker官方文档翻译4

第四篇&#xff1a;Swarms准备工作安装Docker版本1.13或更高版本。安装Docker compose。安装docker machine阅读第1 2 3部分的内容。确保你已发布并推送到注册仓库的friendlyhello镜像。确保你的镜像可以部署为一个容器。 运行这个命令&#xff0c;在你的信息中插入用户名&…

编写第一个Java程序:helloworld

配置好java开发环境后&#xff0c;我们可以编写第一个java程序&#xff08;helloworld&#xff09;。 在开始编写java程序时&#xff0c;我们要知道java程序开发的三个步骤&#xff1a;编写、编译、运行。 编写阶段主要是由程序员编写后缀为.java的文件&#xff1b;将后缀为.ja…

java中关键字、标识符、常量、变量、数据类型

关键字&#xff1a; JAVA语言中已经定义好的具有特殊含义的单词&#xff0c;被称为关键字&#xff0c;关键字是全小写无特殊字符的纯 英文字母&#xff0c;在编辑器中一般有高亮效果&#xff0c;如public、class、static等。 标识符&#xff1a; 在JAVA语言中&#xff0c;自…

js滚动条下拉一定值_JS逆向 | 无限Debugger之淘大象

置顶公众号今天继续和大家研究JS逆向&#xff0c;不少小伙伴在JS逆向的时候遇到过无限debugger的反爬&#xff0c;今天就拿一个网站练练手感受下无限debugger。分析请求先打开这次的目标网站--淘大象(https://taodaxiang.com/credit2)打开「开发者工具」就自动进入debug&#x…

java中数据类型转换、ASCII编码

数据类型转换&#xff1a; JAVA语言中要求参与计算的数据类型要保持一致&#xff0c;如果不一致则会发生数据类型转换&#xff0c;数据类型转换可分为&#xff1a;自动类型转换&#xff08;隐式转换&#xff09;和强制类型转换&#xff08;显式转换&#xff09;。 自动类型转…

pyecharts x轴字体大小调整_pyecharts 柱状图基础篇#学习笔记#

2020年初&#xff0c;很久没有用过pyecharts的我由于工作原因&#xff0c;安装了新版pyecharts之后&#xff0c;以前的pyecharts代码报错了。搜索之后才发现&#xff0c;我安装的是不兼容旧版本的新版。空闲的时间&#xff0c;把新版echart当作新的模块梳理了一下&#xff0c;整…

JAVA语言运算符(算数运算符、赋值运算符、比较运算符、逻辑运算符、三元运算)

运算符 JAVA语言中将运算符分为&#xff1a;算数运算符、赋值运算符、比较运算符、逻辑运算符、三元运算符。 算数运算符&#xff1a;算数运算符是对数字进行一系列的加减乘除等的计算&#xff0c;常见的算数运算符如&#xff1a; public class Operation{public static vo…

真彩色图像数据量 计算_军职在线大学计算机基础(自主模式)

第一章单选题电子计算机的发展已经经过了4代&#xff0c;4代计算机的主要元器件分别是(b)a.电子管&#xff0c;晶体管&#xff0c;中小规模集成电路&#xff0c;激光器件b.电子管&#xff0c;晶体管&#xff0c;中小规模集成电路&#xff0c;大规模或超大规模集成电路 c.晶体管…

JAVA语言中流程控制(顺序结构、判断语句、循环语句)

流程控制&#xff1a; 程序在执行的过程中&#xff0c;各条语句的执行顺序对程序的结果是有直接影响的&#xff0c;所以&#xff0c;我们必须清楚每条语句的执行流程。而且&#xff0c;很多时候我们要通过控制语句的执行顺序来实现想要的功能。java中流程控制有&#xff1a;顺…

java中JShell脚本工具、常量优化、集成开发环境IDEA

JShell脚本工具&#xff1a; jshell:直接在DOS命令行&#xff08;黑窗口&#xff09;键入main方法中的代码就可以快速看到运行的结果&#xff0c;无需编译再运行&#xff0c;并且省略了入口方法及定义类的过程&#xff0c;其使用方法&#xff1a; 1.打开黑窗口后键入&#xf…

python编写arcgis脚本教程_ArcGIS使用Python脚本工具

在Pyhton写的一些代码&#xff0c;用户交互不方便&#xff0c;用户体验比较差&#xff0c;不方便重用。在ArcGIS中可以将用写的Python代码导入到ToolBox中&#xff0c;这样用起来就比较方便了。这里用按要素裁剪栅格的Python来演示如何导入ArcGIS中。代码如下&#xff1a; impo…

python中可以使用变量来引用函数吗_如何在python语言中使用函数变量并调用函数...

在python语言中&#xff0c;除了常规变量之外&#xff0c;还有函数变量。把函数本身赋值给变量&#xff0c;这个变量为函数变量。工具/原料 python pycharm 截图工具 WPS 方法/步骤 1 在已新建的python文件中&#xff0c;定义函数mul_data&#xff0c;传入两个参数param和exp2 …

C语言程序设计现代方法1,2,3章

1&#xff1a;浮点型&#xff08;float&#xff09;运算比int慢&#xff0c;并且可能存在舍入误差 如float存储0.1&#xff0c;以后使用可能会变成0.099999999987 2&#xff1a;宏定义只用大写&#xff0c;这是大多数C程序猿遵循的规范&#xff01; C语言区分大小写&#xff0c…

初识java中数组、数组在内存中、越界异常、空指针异常

数组&#xff1a; 当存储多个数据时&#xff0c;此时可能会使用多个变量&#xff0c;这样不断声明变量会很麻烦&#xff0c;而这些变量都有相似的特性&#xff0c;因此我们可以将它们存放到一个容器中&#xff0c;统一处理。 容器&#xff1a;是将多个数据存储到一起&#xf…