最近在学习ZRender,同时也在做一个基于ZRender的开源小项目,利用库封装好的canvas API,来实现一套通用的业务流程建模图。
什么是ZRender就不解释了,请参阅官方文档
为了更好的使用ZRender这个工具,我开始阅读一些源码,打算写一个系列博客,希望能坚持下去。这是系列第一篇,我打算从小处着手,先聊聊Eventful这个类。
/** * 事件扩展 * @module zrender/mixin/Eventful */
...
/** * 事件分发器 * @alias module:zrender/mixin/Eventful * @constructor */
var Eventful = function () {
this._$handlers = {};
};
Eventful.prototype = {
...
/** * 绑定事件 * @param {string} event 事件名 * @param {Function} handler 事件处理函数 * @param {Object} [context] */
on: function (event, handler, context) {
var _h = this._$handlers;
...
if (!_h[event]) {
_h[event] = [];
}
.....
for (var i = 0; i < _h[event].length; i++) {
if (_h[event][i].h === handler) {
return this;
}
}
},
/** * 解绑事件 * @param {string} event 事件名 * @param {Function} [handler] 事件处理函数 */
off: function (event, handler) {...}
}
你会发现好的开源项目,注释写的清晰明了并且恰到好处。通过上面的代码片段,可以很容易看出这个函数就是实现自定义事件的绑定,触发以及解绑,很明显的发布订阅模式。这方面的文章汗牛充栋,我就不多说了。真正带给我感悟的是这个函数或者类如何与对象关联的,即让对象Eventful起来的实现方式。
首先来看Eventful.js这个文件的路径:zrender/src/mixin/Eventful.js
mixin这个关键词让我虎躯一震,因为mixin这个概念很早就在Extjs中出现过:
Mixins allows us to use functions of one class as a function of another class without inheritance
翻译过来就是Mixin赋予我们这样一种能力:一个类可以不通过继承来调用来自另外一个类的方法。其实还有一层意思没有明确表达出来,那就是一个类可以不通过继承来调用来自另外一个或者很多类的方法。
在javascript中一个类想拥有另外一个类的方法属性,一般是通过原型链prototype模拟继承。ES6的class extends关键字本质上是原型链继承的一个语法糖。但是extends后面只能跟一个类,也就是说javascript中不能多继承,这一点和java, C#这些面向对象的语言是一致的。
在java和C#中,不能用extend来多继承,但是可以用implement来实现多个接口的方式来处理可能需要“多继承“的场景。很不幸的是javascript中并没有interface这么一个概念。
假设有这么一个业务场景,我的的类不仅需要Eventful的方法,还需要调用otherFuls的方法,Extends不能满足我们的需求,这时mixin就粉墨登场了。我们先看一下ZRender中Eventful在zrender/src/Handler.js中的使用方法:
多扯一句,Hanlder中的proxy处理Zrender的事件比如mousedown, mousemove等等。
import Draggable from './mixin/Draggable';
import Eventful from './mixin/Eventful';
import * as util from './core/util';
var Handler = function(storage, painter, proxy, painterRoot) {
// 将Eventful构造函数中的handlers赋予Handler实例
Eventful.call(this);
.......
}
......
util.mixin(Handler, Eventful);
util.mixin(Handler, Draggable);
Handler.prototype = {.....}
export default Handler;
亮点在最后的两个mixin调用,可以看出通过mixin的方式,Handler具备了Eventful和Draggable的能力。
接下来我们看一下mixin的实现方式zrender/src/core/util.js
export function mixin(target, source, overlay) {
target = 'prototype' in target ? target.prototype : target;
source = 'prototype' in source ? source.prototype : source;
defaults(target, source, overlay);
}
export function defaults(target, source, overlay) {
for (var key in source) {
if (source.hasOwnProperty(key)
&& (overlay ? source[key] != null : target[key] == null)
) {
target[key] = source[key];
}
}
return target;
}
过滤到一些边界条件,可以看出mixin的实现非常简单,就是通过浅拷贝将source原型上的方法放到target类。
实现很简单,但是mixin反映出的关于多继承或者也可以叫做js版”interface”的思考很有意义和学习价值!