最近在学习 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" 的思考很有意义和学习价值!
来源: https://www.2cto.com/kf/201806/755842.html