作为异步驱动编程模型,事件无疑是JavaScript不可或许的部分。

在JavaScript所定义的事件中,它们被分为多种类型。有 表单事件(focus、blur、submit等)、window事件(load、unload、resize等)、鼠标事件(mousedown、mousmove、mousup等),键盘类型(keydown、keyup等)。

针对移动端,还有触摸类型,比如 touchstart、touchmove、touchend、touchcancel 等。另外,HTML5还有各类API事件,比如 dragstart、drag、dragend 等。

当然,上面所涉及到类型,都是系统提供的。要响应点击事件,我们通常会这样:

element.addEventListener('click', clickFn);

当用户点击 element 时,就会触发函数 clickFn。但你有没有想过,为什么当我们点击元素后,就会触发绑定的函数呢?

换言之,如果把 click 事件换成我们自定义的事件,比如系统没有提供的长按事件,回调函数也会执行吗?

这里假设用户点击元素,按住时间超过1秒,即为 长按,我们定义事件名为 longpress。当用户执行下面代码,longpressFn 能否也能触发?

element.addEventListener('longpress', longpressFn);

若你运行过上面的代码,你会发现 longpressFn 并不会执行。原因是,longpress 并非是JavaScript定义的事件,而是我们自定义的。因此无法识别,longpressFn 自然也不会触发。

这里的 longpress 就是所谓的 “自定义事件”,也被称为 “模拟事件”。

现在,我们面临的问题是如何让自定义的 longpress 事件生效。而要解决这个问题,通常需要经历 创建自定义事件触发自定义事件 这两步。

创建自定义事件

JavaScript提供了自定义事件的API,即 CustomEvent(),它的使用语法如下:

var evt = new CustomEvent(typeArg, customEventInit);

其中,typeArg 是一个字符串,表示要创建的事件类型。而 customEventInit 是一个可选对象,它包含以下几个属性:

  • detail 任意的数据类型,表示事件初始化时,需要传递的数据,默认值为 null
  • bubbles 布尔值,表示该事件能否冒泡,默认值为 false
  • cancelable 布尔值,表示该事件是否可以取消,默认值为 false

所以,我们可用使用以下代码去创建一个自定义事件:

var evt = new CustomEvent(type, {detail: msg, bubbles: true, cancelable: true});

旧版写法

而针对旧版浏览器,创建事件还有一个已废弃的写法(有的旧浏览器支持)。你必须先在 document 对象上使用 createEvent 来创建一个新的 event对象,如:

var evt = document.createEvent(type);

其中,evt 是被创建的 Event对象type 是一个字符串,表示要创建的事件类型。它包含 “DOM2级事件” 和 “DOM3级事件”,还有其他的规范定义(如SVG),另外,每个事件类型都有其对应的初始化方法。下面列出部分:

DOM2级

事件模块 事件类型 事件初始化方法
UI事件 “UIEvents” event.initUIEvent
鼠标事件 “MouseEvents” event.initMouseEvent
DOM变动事件 “MutationEvents” event.initMutationEvent
HTML事件 “HTMLEvents” event.initEvent

DOM3级

事件模块 事件类型 事件初始化方法
自定义事件 “CustomEvent” event.initCustomEvent

更多事件类型,可参见 相关文档

这里,我们要自定义事件,选择的事件类型为 CustomEvent,所以需要这样创建:

var evt = document.createEvent('CustomEvent');

对应的事件初始化,就是这样的:

evt.initCustomEvent(type, canBubble, cancelable, detail);

其中,type 为字符串,表示事件类型。bubbles 为布尔值,决定是否事件是否应该向上冒泡。cancelable 决定该事件的默认动作是否可以被取消,即 是否可以用 preventDefault() 方法取消事件。detail 表示初始化事件时,需要传递的数据。

触发自定义事件

定义完事件后,接下来便是触发该事件了。也就是在什么时机下,触发下面的 longpressFn 函数:

element.addEventListener('longpress', longpressFn);

与繁杂的 创建自定义事件 事件相比,触发回调函数十分简单,只需要调用 dispatchEvent 函数,像这样:

element.dispatchEvent(evt);

此时,自定义事件绑定的回调函数便会执行。

结合上面的 创建自定义事件,我们进行简单封装:

function triggerCustomEvent(element, type, msg) {
var evt;

try {
evt = new CustomEvent(type, {detail: msg, bubbles: true, cancelable: true});
} catch (e) { // 旧写法
evt = document.createEvent('CustomEvent');
evt.initCustomEvent(this.type, true, true, msg);
}

element.dispatchEvent(evt);
}

实现一个长按事件

通过前面文章的描述,我们可以轻松的实现一个自定义的长按事件:

var longpressTimer, mousedownTime;

element.addEventListener('longpress', function(e) {
console.log(e.detail);
});

element.addEventListener('mousedown', function() {
mousedownTime = new Date()/1;
longpressTimer = setTimeout(function() {
triggerCustomEvent(element, 'longpress', '你长按了!');
}, 1000);
});

element.addEventListener('mouseup', function() {
if (new Date()/1 - mousedownTime <= 1000) clearTimeout(longpressTimer);
});

上面的代码中,记录分别读取了鼠标按下和鼠标释放事件的时间,若这两个事件的时间间隔超过一秒时,便触发自定义事件的回调函数。

当你在 element 元素上长按时间超过一秒时,控制台会打印出 你长按了!

这样,一个自定义的长按事件就完成了。

IE中的自定义事件

虽说前面的自定义事件看似已经比较完美了,但很可惜,针对前面提到的 创建自定义事件触发自定义事件, IE并不支持。

有人可能知道,要在 IE 浏览器中完成事件模拟,可以把 document.createEventObjectfireEvent 结合起来。像这样:

var evt;

evt = document.createEventObject();
evt.detail = msg;

element.fireEvent('on' + type, evt);

但很快你就会发现,这里的 fireEvent 就是一坑货。因为它只支持IE已经定义了的事件,并不能实现真正意义上的实现自定义事件。

如果非要在IE中实现自定义事件,也是有办法的。其中,网上比较流行的一种做法,是利用IE的私有事件 propertychange

原理:如果元素绑定了 propertychange 事件,那么,只要该元素的属性发生变化,propertychange 所对应的回调函数就能触发。这样,我们便可以将自定义事件(longpress)所对应的回调函数(longpressFn),放在 propertychange 的回调函数中调用。

用代码简单描述下:

...

element.attachEvent('longpress', longpressFn);

element.attachEvent('onpropertychange', function() {
longpressFn();
});

element.attachEvent('mousedown', function() {
timer = setTimeout(function() {
element.setAttribute('time', new Date()/1);
}, 1000);
});

....

如果希望尽可能多的兼容,可将标准DOM和IE的自定义事件再进行组合封装。

以上,就是JavaScript自定义事件。