上次我们对canvas的canvasPainter.js中的最后一部分做了分析总结,对于Quark Renderer渲染引擎来讲,它都是一个元素。那这些元素里有好多的属性,每个元素上面都有,比如说它当前的位置、缩放的状态、颜色、阴影、渐变等可视的属性。我们之前几次学习都是对canvas中的js文件进行了深度的解析,对于一些canvas中调用的其他文件也进行了学习。那么这次,我们对下一部分进行分析学习。
我们这次的学习部分是事件系统处理,事件系统也是比较有意思的一点,因为Canvas是一个很低级的很底层的API的接口,没有提供一些高层级的这种封装,那就意味着你要去做一些事件的时候就很麻烦,必须自己实现。通过对事件event包中的代码内容进行整体查看分析,可以得知整个Quark Renderer的事件系统是通过模拟DOM事件的设计来进行对事件的处理的。
事件系统里比较精华的部分就在Eventful.js的包里。所有跟事件相关的东西,都在该包中,负责对事件系统进行一些全局把控。该包中的函数也为不支持事件机制的类提供事件支持,其基本机制类似 W3C DOM 事件,需要事件机制的类可以 mixin 此类中的工具函数。
首先是构造函数,需要参数,分别是一个对象,作为事件处理者,也就是当前事件处理函数执行时的作用域;还有一个函数,需要方法返回true才会执行;还有一个方法,该方法会在事件处理函数被调用之后执行;最后一个参数也是一个函数,该方法在添加或者删除事件监听的时候被调用。
let Eventful = function (eventProcessor) {
this._$handlers = {};
this._$eventProcessor = eventProcessor;
this._$suspends = new Set();
};
该处理函数只会被调用一次,使用完然后就会被删除。该函数有四个参数,分别是event(事件名)、query(事件过滤条件)、handler(事件处理函数)、context(事件处理函数执行的上下文),在该函数中返回的值调用了on函数。下面我们分析on函数。
one: function (event, query, handler, context) {
return on(this, event, query, handler, context, true);
},
该方法的作用是绑定事件处理函数,传入四个参数,分别是事件名、条件过滤条件、事件处理函数、以及事件处理函数执行的上下文。返回时on函数。
on: function (event, query, handler, context) {
return on(this, event, query, handler, context, false);
},
在on函数中,我们看到他传入的值比one更多一个,是一个boolean值,表示是否只执行一次,我们传入的是true,其他参数都是一样的。在下面的函数代码中我们可以看到,有三个判断,分别对传入的参数进行了分析判断,第一个if是判断query是否为function,第二个参数是判断handler和event是否为不为null,第三个判断是对_h[event]进行判断,如果不存在就将该值设置为一个数组。
let _h = eventful._$handlers;
if (typeof query === 'function') {
context = handler;
handler = query;
query = null;
}
if (!handler || !event) {
return eventful;
}
if (!_h[event]) {
_h[event] = [];
}
在剩余部分中,对_h[event]进行遍历,判断他们的ctx和h是否对应context和handler,返回eventful。
然后定义一个wrap对象,将值都设置为在该方法中得到的参数。然后得到他们lastIndex和lastWrap,调用callListenerChanged函数,该函数主要对ventful._$eventProcessor.afterListenerChanged赋值为eventType
for (let i = 0; i < _h[event].length; i++) {
if (_h[event][i].ctx === context && _h[event][i].h === handler) {
return eventful;
}
}
let wrap = {
h: handler,
one: isOnce,
query: query,
ctx: context || eventful,
callAtLast: handler.qrEventfulCallAtLast,
};
let lastIndex = _h[event].length - 1;
let lastWrap = _h[event][lastIndex];
lastWrap && lastWrap.callAtLast ? _h[event].splice(lastIndex, 0, wrap) : _h[event].push(wrap);
callListenerChanged(eventful, event);
return eventful;
有on函数就会有off方法与之对应,该方法的作用就是解除事件处理函数,需要传入的参数有三个,分别是事件名,如果参数为null,所有事件监听器都会被删除;事件处理函数handle,如果参数为null,所有事件监听器都会被删除;最后一个参数就是上下文context,显示页面。
该函数的主要操作时分别对参数进行判断,是否为null,然后进行上述的操作,确定删除的事件处理函数,在方法体中,我们可以看到使用了for循环来进行对绑定事件的遍历,找出符合不移除的事件,移入新的list中,然后删除原来的事件队列。
off: function (event, handler, context) {
let _h = this._$handlers;
if (!event) {
this._$handlers = {};
return this;
}
if (handler) {
if (_h[event]) {
let newList = [];
for (let i = 0, l = _h[event].length; i < l; i++) {
if (_h[event][i].ctx !== context && _h[event][i].h !== handler) {
newList.push(_h[event][i]);
}
}
_h[event] = newList;
}
if (_h[event] && _h[event].length === 0) {
delete _h[event];
}
} else {
delete _h[event];
}
callListenerChanged(this, event);
return this;
},
除了这些函数之外,还有一些其他的函数,负责对事件的触发等发挥作用,如isSilene、suspend、resume等。isSilent方阿飞的作用是判断是否绑定了事件处理函数,返回的是处理函数的长度,如果为0就是未绑定,也就是未绑定。suspend函数的作用是挂起一个事件,被挂起的事件的不会被触发,在鼠标和触摸屏交互的过程中,经常需要把某个事件临时挂起以避免误触。而resume方法的作用是恢复触发,与suspend方法相对应。
isSilent: function (event) {
let _h = this._$handlers;
return !_h[event] || !_h[event].length;
},
suspend: function (eventName) {
this._$suspends.add(eventName);
},
resume: function (eventName) {
this._$suspends.delete(eventName);
},
最后一个方法,该方法时trigger,该方法的作用时触发一个事件,主要是对传入的参数(事件名)进行处理,先判断是否存在该事件名,然后再找出该事件,接着通过switch进行匹配。
if (this._$suspends.has(eventName)) {
return;
}
let _h = this._$handlers[eventName];
let eventProcessor = this._$eventProcessor;
这是进行对查到的arglen进行长度匹配,从骨干网优化建议,对长度进行匹配。
let args = arguments;
let argLen = args.length;
switch (argLen) {
case 1:
hItem.h.call(hItem.ctx);
break;
case 2:
hItem.h.call(hItem.ctx, args[1]);
break;
case 3:
hItem.h.call(hItem.ctx, args[1], args[2]);
break;
case 4:
hItem.h.call(hItem.ctx, args[1], args[2], args[3]);
break;
case 5:
hItem.h.call(hItem.ctx, args[1], args[2], args[3], args[4]);
break;
default:
hItem.h.apply(hItem.ctx, args);
break;
}
以上便是Eventful.js中的所有方法,再进行完整体分析后,我们从每个方法的实际作用展开的讨论,对于关键代码进行细节分析。总体而言,该js文件负责事件处理的核心,对于canvasRender的事件处理有着至关重要的作用。