Skip to content

Commit 816d76d

Browse files
fix: Keyboard event memory leak (#469)
* fix: Keyboard event memory leak * fix: add dist
1 parent c96985c commit 816d76d

File tree

8 files changed

+434
-136
lines changed

8 files changed

+434
-136
lines changed

dist/hotkeys.common.js

Lines changed: 114 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* hotkeys-js v3.13.3
33
* A simple micro-library for defining and dispatching keyboard shortcuts. It has no dependencies.
44
*
5-
* Copyright (c) 2023 kenny wong <[email protected]>
5+
* Copyright (c) 2024 kenny wong <[email protected]>
66
* https://github.com/jaywcjlove/hotkeys-js.git
77
*
88
* @website: https://jaywcjlove.github.io/hotkeys-js
@@ -19,9 +19,14 @@ function addEvent(object, event, method, useCapture) {
1919
if (object.addEventListener) {
2020
object.addEventListener(event, method, useCapture);
2121
} else if (object.attachEvent) {
22-
object.attachEvent("on".concat(event), () => {
23-
method(window.event);
24-
});
22+
object.attachEvent("on".concat(event), method);
23+
}
24+
}
25+
function removeEvent(object, event, method, useCapture) {
26+
if (object.removeEventListener) {
27+
object.removeEventListener(event, method, useCapture);
28+
} else if (object.deachEvent) {
29+
object.deachEvent("on".concat(event), method);
2530
}
2631
}
2732

@@ -156,9 +161,9 @@ for (let k = 1; k < 20; k++) {
156161
}
157162

158163
let _downKeys = []; // 记录摁下的绑定键
159-
let winListendFocus = false; // window是否已经监听了focus事件
164+
let winListendFocus = null; // window是否已经监听了focus事件
160165
let _scope = 'all'; // 默认热键范围
161-
const elementHasBindEvent = []; // 已绑定事件的节点记录
166+
const elementEventMap = new Map(); // 已绑定事件的节点记录
162167

163168
// 返回键码
164169
const code = x => _keyMap[x.toLowerCase()] || _modifier[x.toLowerCase()] || x.toUpperCase().charCodeAt(0);
@@ -235,7 +240,17 @@ function deleteScope(scope, newScope) {
235240
if (Object.prototype.hasOwnProperty.call(_handlers, key)) {
236241
handlers = _handlers[key];
237242
for (i = 0; i < handlers.length;) {
238-
if (handlers[i].scope === scope) handlers.splice(i, 1);else i++;
243+
if (handlers[i].scope === scope) {
244+
const deleteItems = handlers.splice(i, 1);
245+
deleteItems.forEach(_ref2 => {
246+
let {
247+
element
248+
} = _ref2;
249+
return removeKeyEvent(element);
250+
});
251+
} else {
252+
i++;
253+
}
239254
}
240255
}
241256
}
@@ -271,6 +286,7 @@ function unbind(keysInfo) {
271286
// unbind(), unbind all keys
272287
if (typeof keysInfo === 'undefined') {
273288
Object.keys(_handlers).forEach(key => delete _handlers[key]);
289+
removeKeyEvent(null);
274290
} else if (Array.isArray(keysInfo)) {
275291
// support like : unbind([{key: 'ctrl+a', scope: 's1'}, {key: 'ctrl-a', scope: 's2', splitKey: '-'}])
276292
keysInfo.forEach(info => {
@@ -300,13 +316,13 @@ function unbind(keysInfo) {
300316
}
301317

302318
// 解除绑定某个范围的快捷键
303-
const eachUnbind = _ref2 => {
319+
const eachUnbind = _ref3 => {
304320
let {
305321
key,
306322
scope,
307323
method,
308324
splitKey = '+'
309-
} = _ref2;
325+
} = _ref3;
310326
const multipleKeys = getKeys(key);
311327
multipleKeys.forEach(originKey => {
312328
const unbindKeys = originKey.split(splitKey);
@@ -317,11 +333,15 @@ const eachUnbind = _ref2 => {
317333
// 判断是否传入范围,没有就获取范围
318334
if (!scope) scope = getScope();
319335
const mods = len > 1 ? getMods(_modifier, unbindKeys) : [];
336+
const unbindElements = [];
320337
_handlers[keyCode] = _handlers[keyCode].filter(record => {
321338
// 通过函数判断,是否解除绑定,函数相等直接返回
322339
const isMatchingMethod = method ? record.method === method : true;
323-
return !(isMatchingMethod && record.scope === scope && compareArray(record.mods, mods));
340+
const isUnbind = isMatchingMethod && record.scope === scope && compareArray(record.mods, mods);
341+
if (isUnbind) unbindElements.push(record.element);
342+
return !isUnbind;
324343
});
344+
unbindElements.forEach(element => removeKeyEvent(element));
325345
});
326346
};
327347

@@ -445,10 +465,12 @@ function dispatch(event, element) {
445465
}
446466
// key 不在 _handlers 中返回
447467
if (!(key in _handlers)) return;
448-
for (let i = 0; i < _handlers[key].length; i++) {
449-
if (event.type === 'keydown' && _handlers[key][i].keydown || event.type === 'keyup' && _handlers[key][i].keyup) {
450-
if (_handlers[key][i].key) {
451-
const record = _handlers[key][i];
468+
const handlerKey = _handlers[key];
469+
const keyLen = handlerKey.length;
470+
for (let i = 0; i < keyLen; i++) {
471+
if (event.type === 'keydown' && handlerKey[i].keydown || event.type === 'keyup' && handlerKey[i].keyup) {
472+
if (handlerKey[i].key) {
473+
const record = handlerKey[i];
452474
const {
453475
splitKey
454476
} = record;
@@ -465,11 +487,6 @@ function dispatch(event, element) {
465487
}
466488
}
467489
}
468-
469-
// 判断 element 是否已经绑定事件
470-
function isElementBind(element) {
471-
return elementHasBindEvent.indexOf(element) > -1;
472-
}
473490
function hotkeys(key, option, method) {
474491
_downKeys = [];
475492
const keys = getKeys(key); // 需要处理的快捷键列表
@@ -528,21 +545,35 @@ function hotkeys(key, option, method) {
528545
});
529546
}
530547
// 在全局document上设置快捷键
531-
if (typeof element !== 'undefined' && !isElementBind(element) && window) {
532-
elementHasBindEvent.push(element);
533-
addEvent(element, 'keydown', e => {
534-
dispatch(e, element);
535-
}, capture);
548+
if (typeof element !== 'undefined' && window) {
549+
if (!elementEventMap.has(element)) {
550+
const keydownListener = function () {
551+
let event = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : window.event;
552+
return dispatch(event, element);
553+
};
554+
const keyupListenr = function () {
555+
let event = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : window.event;
556+
dispatch(event, element);
557+
clearModifier(event);
558+
};
559+
elementEventMap.set(element, {
560+
keydownListener,
561+
keyupListenr,
562+
capture
563+
});
564+
addEvent(element, 'keydown', keydownListener, capture);
565+
addEvent(element, 'keyup', keyupListenr, capture);
566+
}
536567
if (!winListendFocus) {
537-
winListendFocus = true;
538-
addEvent(window, 'focus', () => {
568+
const listener = () => {
539569
_downKeys = [];
540-
}, capture);
570+
};
571+
winListendFocus = {
572+
listener,
573+
capture
574+
};
575+
addEvent(window, 'focus', listener, capture);
541576
}
542-
addEvent(element, 'keyup', e => {
543-
dispatch(e, element);
544-
clearModifier(e);
545-
}, capture);
546577
}
547578
}
548579
function trigger(shortcut) {
@@ -556,6 +587,58 @@ function trigger(shortcut) {
556587
});
557588
});
558589
}
590+
591+
// 销毁事件,unbind之后判断element上是否还有键盘快捷键,如果没有移除监听
592+
function removeKeyEvent(element) {
593+
const values = Object.values(_handlers).flat();
594+
const findindex = values.findIndex(_ref4 => {
595+
let {
596+
element: el
597+
} = _ref4;
598+
return el === element;
599+
});
600+
if (findindex < 0) {
601+
const {
602+
keydownListener,
603+
keyupListenr,
604+
capture
605+
} = elementEventMap.get(element) || {};
606+
if (keydownListener && keyupListenr) {
607+
removeEvent(element, 'keyup', keyupListenr, capture);
608+
removeEvent(element, 'keydown', keydownListener, capture);
609+
elementEventMap.delete(element);
610+
}
611+
}
612+
if (values.length <= 0 || elementEventMap.size <= 0) {
613+
// 移除所有的元素上的监听
614+
const eventKeys = Object.keys(elementEventMap);
615+
eventKeys.forEach(el => {
616+
const {
617+
keydownListener,
618+
keyupListenr,
619+
capture
620+
} = elementEventMap.get(el) || {};
621+
if (keydownListener && keyupListenr) {
622+
removeEvent(el, 'keyup', keyupListenr, capture);
623+
removeEvent(el, 'keydown', keydownListener, capture);
624+
elementEventMap.delete(el);
625+
}
626+
});
627+
// 清空 elementEventMap
628+
elementEventMap.clear();
629+
// 清空 _handlers
630+
Object.keys(_handlers).forEach(key => delete _handlers[key]);
631+
// 移除window上的focus监听
632+
if (winListendFocus) {
633+
const {
634+
listener,
635+
capture
636+
} = winListendFocus;
637+
removeEvent(window, 'focus', listener, capture);
638+
winListendFocus = null;
639+
}
640+
}
641+
}
559642
const _api = {
560643
getPressedKeyString,
561644
setScope,

dist/hotkeys.common.min.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)