在 Node.js 中,我们可以使用 events 模块方便地创建、监听、发送事件。 对于浏览器而言,同样有一套基于 DOM 的事件相关 API 可供使用。
未标准化之前
创建事件
var event = document.createEvent(type);
返回 DOM Event
对象,type
可以是 "UIEvents"
、"MouseEvent"
、"KeyboardEvent"
等。
初始化事件
根据创建时传入的 type,调用对应的初始化函数:
event.initEvent(type, bubbles, cancelable)
event.initUIEvent(type, canBubble, cancelable, view, detail)
event.initMouseEvent(type, canBubble, cancelable, view, detail, screenX, screenY, clientX, clientY, ctrlKey, altKey, shiftKey, metaKey, button, relatedTarget)
event.initKeyboardEvent(typeArg, canBubbleArg, cancelableArg, viewArg, charArg, keyArg, locationArg, modifiersListArg, repeat)
- 等等
以键盘事件为例:
var event = document.createEvent('KeyboardEvent');
event.initKeyboardEvent();
那些不统一的初始化参数
DOM 2 Events 规范经过多次修改,initKeyboardEvent()
中的参数也几经变迁,如今已经被标记为 deprecated。
Chrome / Safari / IE 中依然可以调用 initKeyboardEvent()
,不过参数不尽相同,即使是 Webkit 内核,不同版本也有不同参数(这里的 polyfill 中有一些研究)。
Firefox(Gecko 内核)已经移除 initKeyboardEvent()
方法,可以使用 initKeyEvent()
代替。
initKeyEvent
使用示例(仅 Firefox 有效):
// create a key event
var event = document.createEvent('KeyboardEvent');
// define the event (Press `Shift` + `X`)
event.initKeyEvent(
'keypress', // typeArg,
true, // canBubbleArg,
true, // cancelableArg,
null // viewArg, Specifies UIEvent.view. This value may be null.
false, // ctrlKeyArg,
false, // altKeyArg,
true, // shiftKeyArg,
false, // metaKeyArg,
88, // keyCodeArg,
88 // charCodeArg
);
// dispatch the event
document.dispatchEvent(event);
IE 中 initKeyboardEvent()
的详细参数,详见MSDN - initKeyboardEvent method。
需要注意的是其中的 modifiersListArg
,类型为 string
,举例:'Control Shift Alt'
(中间以空格作为间隔)表示同时按下了 Control、Shift、Alt 这三个键。
initKeyboardEvent
使用示例(仅 IE9+ 有效):
// create a key event
var event = document.createEvent('KeyboardEvent');
// define the event (Press `Shift` + `X`)
event.initKeyEvent(
'keypress', // eventType,
true, // canBubble,
true, // cancelable,
null // viewArg,
'X', // keyArg,
0, // locationArg,
'Shift', // modifiersListArg,
false, // repeat,
'', // locale
);
// dispatch the event
document.dispatchEvent(event);
标准化之后
创建、初始化事件
目前的 W3C 规范推荐使用事件构造函数创建事件。
var event = new Event(typeArg, eventInit);
var keyboardEvent = new KeyboardEvent(typeArg, KeyboardEventInit);
依然以创建键盘事件为例,向构造函数传入事件类型和一个初始化字典:
var keyboardEvent = new KeyboardEvent('keydown', {
altKey: true,
bubbles: true,
cancelable: true,
code: 'KeyK',
composed: true,
ctrlKey: true,
key: 'k',
metaKey: true,
repeat: true,
shiftKey: true,
view: window
});
向前兼容
目前,除 Firefox 外,在 Chrome / Safari 中存在bug,无论是 initKeyboardEvent()
还是使用构造函数,都无法初始化事件的 charCode
、keyCode
、which
属性,它们始终为 0
。详情可以查看相关 Issue:
由于最新的规范里 charCode
、keyCode
、which
均被废弃,所以 Webkit 和 Chromium 宣称不会进行修复。而 key
、code
尚未被所有浏览器支持,众多的项目也多是判断 charCode
、keyCode
。
可以使用 Object.defineProperties()
设置事件的 charCode
、keyCode
、which
(这也是 Chrome 团队 Paul Irish 推荐的做法)。
var keyboardEvent = new KeyboardEvent('keydown', {
altKey: true,
bubbles: true,
cancelable: true,
code: 'KeyK',
composed: true,
ctrlKey: true,
key: 'k',
metaKey: true,
repeat: true,
shiftKey: true,
view: window
});
Object.defineProperties(keyboardEvent, {
charCode: {
get: function () {
// 只有 keypress 事件和可打印字符才返回有效的 charCode
if (this.type === 'keypress' && this.key && this.key.length === 1) {
return this.key.charCodeAt(0);
}
return 0;
}
},
keyCode: {
get: function () {
if (this.key && this.key.length === 1) {
if (this.type === 'keypress') {
return this.key.charCodeAt(0);
}
// 数字和大写字母的 keyCode 恰好对应 ASCII 值,英文标点的键值视浏览器而定
return this.key.toUpperCase().charCodeAt(0);
} else {
console.error('暂不支持获取keyCode值!');
return 0;
}
}
},
which: {
get: function () {
if (this.key && this.key.length === 1) {
if (this.type === 'keypress') {
return this.key.charCodeAt(0);
}
// 数字和大写字母的 which 恰好对应 ASCII 值,英文标点的键值视浏览器而定
return this.key.toUpperCase().charCodeAt(0);
} else {
console.error('暂不支持获取which值!');
return 0;
}
}
}
});
console.log('key', keyboardEvent.key);
console.log('charCode', keyboardEvent.charCode);
console.log('keyCode', keyboardEvent.keyCode);
console.log('which', keyboardEvent.which);
其他
发送以及监听事件
事件的发送和监听对前端开发者来说再熟悉不过。
发送事件可以使用 dispatchEvent()
(IE9 之前的浏览器可以使用 fireEvent()
):
document.dispatchEvent(event);
事件监听使用 addEventListener()
(IE9 之前的浏览器可以使用 attachEvent()
):
document.addEventListener(type, listener[, useCapture])
取消事件监听 removeEventListener()
(IE9 之前的浏览器可以使用 detachEvent()
):
document.removeEventListener(type, listener[, useCapture]);
番外:如何区分用户动作创建的事件和通过 API 创建的事件
事件对象上存在一个 isTrusted
属性,返回一个布尔值,可以表明事件是由用户操作后产生(比如点击、拖拽)或是由脚本通过 API 创建。