Skip to content

Commit 8610f99

Browse files
brandonocaseygkatsev
authored andcommitted
perf: Use WeakMap for dom data (#6103)
1 parent 1d2b206 commit 8610f99

File tree

7 files changed

+30
-208
lines changed

7 files changed

+30
-208
lines changed

src/js/component.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import window from 'global/window';
77
import evented from './mixins/evented';
88
import stateful from './mixins/stateful';
99
import * as Dom from './utils/dom.js';
10-
import * as DomData from './utils/dom-data';
10+
import DomData from './utils/dom-data';
1111
import * as Fn from './utils/fn.js';
1212
import * as Guid from './utils/guid.js';
1313
import toTitleCase from './utils/to-title-case.js';
@@ -153,7 +153,9 @@ class Component {
153153
this.el_.parentNode.removeChild(this.el_);
154154
}
155155

156-
DomData.removeData(this.el_);
156+
if (DomData.has(this.el_)) {
157+
DomData.delete(this.el_);
158+
}
157159
this.el_ = null;
158160
}
159161

src/js/utils/dom-data.js

Lines changed: 1 addition & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
* @file dom-data.js
33
* @module dom-data
44
*/
5-
import * as Guid from './guid.js';
6-
import window from 'global/window';
75

86
/**
97
* Element Data Store.
@@ -15,85 +13,4 @@ import window from 'global/window';
1513
* @type {Object}
1614
* @private
1715
*/
18-
export const elData = {};
19-
20-
/*
21-
* Unique attribute name to store an element's guid in
22-
*
23-
* @type {String}
24-
* @constant
25-
* @private
26-
*/
27-
const elIdAttr = 'vdata' + Math.floor(window.performance && window.performance.now() || Date.now());
28-
29-
/**
30-
* Returns the cache object where data for an element is stored
31-
*
32-
* @param {Element} el
33-
* Element to store data for.
34-
*
35-
* @return {Object}
36-
* The cache object for that el that was passed in.
37-
*/
38-
export function getData(el) {
39-
let id = el[elIdAttr];
40-
41-
if (!id) {
42-
id = el[elIdAttr] = Guid.newGUID();
43-
}
44-
45-
if (!elData[id]) {
46-
elData[id] = {};
47-
}
48-
49-
return elData[id];
50-
}
51-
52-
/**
53-
* Returns whether or not an element has cached data
54-
*
55-
* @param {Element} el
56-
* Check if this element has cached data.
57-
*
58-
* @return {boolean}
59-
* - True if the DOM element has cached data.
60-
* - False otherwise.
61-
*/
62-
export function hasData(el) {
63-
const id = el[elIdAttr];
64-
65-
if (!id) {
66-
return false;
67-
}
68-
69-
return !!Object.getOwnPropertyNames(elData[id]).length;
70-
}
71-
72-
/**
73-
* Delete data for the element from the cache and the guid attr from getElementById
74-
*
75-
* @param {Element} el
76-
* Remove cached data for this element.
77-
*/
78-
export function removeData(el) {
79-
const id = el[elIdAttr];
80-
81-
if (!id) {
82-
return;
83-
}
84-
85-
// Remove all stored data
86-
delete elData[id];
87-
88-
// Remove the elIdAttr property from the DOM node
89-
try {
90-
delete el[elIdAttr];
91-
} catch (e) {
92-
if (el.removeAttribute) {
93-
el.removeAttribute(elIdAttr);
94-
} else {
95-
// IE doesn't appear to support removeAttribute on the document element
96-
el[elIdAttr] = null;
97-
}
98-
}
99-
}
16+
export default new WeakMap();

src/js/utils/events.js

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @file events.js
88
* @module events
99
*/
10-
import * as DomData from './dom-data';
10+
import DomData from './dom-data';
1111
import * as Guid from './guid.js';
1212
import log from './log.js';
1313
import window from 'global/window';
@@ -23,7 +23,10 @@ import document from 'global/document';
2323
* Type of event to clean up
2424
*/
2525
function _cleanUpEvents(elem, type) {
26-
const data = DomData.getData(elem);
26+
if (!DomData.has(elem)) {
27+
return;
28+
}
29+
const data = DomData.get(elem);
2730

2831
// Remove the events of a particular type if there are none left
2932
if (data.handlers[type].length === 0) {
@@ -48,7 +51,7 @@ function _cleanUpEvents(elem, type) {
4851

4952
// Finally remove the element data if there is no data left
5053
if (Object.getOwnPropertyNames(data).length === 0) {
51-
DomData.removeData(elem);
54+
DomData.delete(elem);
5255
}
5356
}
5457

@@ -250,7 +253,11 @@ export function on(elem, type, fn) {
250253
return _handleMultipleEvents(on, elem, type, fn);
251254
}
252255

253-
const data = DomData.getData(elem);
256+
if (!DomData.has(elem)) {
257+
DomData.set(elem, {});
258+
}
259+
260+
const data = DomData.get(elem);
254261

255262
// We need a place to store all our handler data
256263
if (!data.handlers) {
@@ -329,11 +336,11 @@ export function on(elem, type, fn) {
329336
*/
330337
export function off(elem, type, fn) {
331338
// Don't want to add a cache object through getElData if not needed
332-
if (!DomData.hasData(elem)) {
339+
if (!DomData.has(elem)) {
333340
return;
334341
}
335342

336-
const data = DomData.getData(elem);
343+
const data = DomData.get(elem);
337344

338345
// If no events exist, nothing to unbind
339346
if (!data.handlers) {
@@ -405,7 +412,7 @@ export function trigger(elem, event, hash) {
405412
// Fetches element data and a reference to the parent (for bubbling).
406413
// Don't want to add a data object to cache for every parent,
407414
// so checking hasElData first.
408-
const elemData = (DomData.hasData(elem)) ? DomData.getData(elem) : {};
415+
const elemData = DomData.has(elem) ? DomData.get(elem) : {};
409416
const parent = elem.parentNode || elem.ownerDocument;
410417
// type = event.type || event,
411418
// handler;
@@ -432,7 +439,10 @@ export function trigger(elem, event, hash) {
432439

433440
// If at the top of the DOM, triggers the default action unless disabled.
434441
} else if (!parent && !event.defaultPrevented && event.target && event.target[event.type]) {
435-
const targetData = DomData.getData(event.target);
442+
if (!DomData.has(event.target)) {
443+
DomData.set(event.target, {});
444+
}
445+
const targetData = DomData.get(event.target);
436446

437447
// Checks if the target has a default action for this event.
438448
if (event.target[event.type]) {

test/unit/component.test.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import window from 'global/window';
33
import Component from '../../src/js/component.js';
44
import * as Dom from '../../src/js/utils/dom.js';
5-
import * as DomData from '../../src/js/utils/dom-data';
5+
import DomData from '../../src/js/utils/dom-data';
66
import * as Events from '../../src/js/utils/events.js';
77
import * as Obj from '../../src/js/utils/obj';
88
import * as browser from '../../src/js/utils/browser.js';
@@ -346,7 +346,7 @@ QUnit.test('should dispose of component and children', function(assert) {
346346
return true;
347347
});
348348
const el = comp.el();
349-
const data = DomData.getData(el);
349+
const data = DomData.get(el);
350350

351351
let hasDisposed = false;
352352
let bubbles = null;
@@ -365,7 +365,7 @@ QUnit.test('should dispose of component and children', function(assert) {
365365
assert.ok(!comp.el(), 'component element was deleted');
366366
assert.ok(!child.children(), 'child children were deleted');
367367
assert.ok(!child.el(), 'child element was deleted');
368-
assert.ok(!DomData.hasData(el), 'listener data nulled');
368+
assert.ok(!DomData.has(el), 'listener data nulled');
369369
assert.ok(
370370
!Object.getOwnPropertyNames(data).length,
371371
'original listener data object was emptied'
@@ -979,7 +979,7 @@ QUnit.test('*AnimationFrame methods fall back to timers if rAF not supported', f
979979
QUnit.test('setTimeout should remove dispose handler on trigger', function(assert) {
980980
const comp = new Component(getFakePlayer());
981981
const el = comp.el();
982-
const data = DomData.getData(el);
982+
const data = DomData.get(el);
983983

984984
comp.setTimeout(() => {}, 1);
985985

@@ -996,7 +996,7 @@ QUnit.test('setTimeout should remove dispose handler on trigger', function(asser
996996
QUnit.test('requestAnimationFrame should remove dispose handler on trigger', function(assert) {
997997
const comp = new Component(getFakePlayer());
998998
const el = comp.el();
999-
const data = DomData.getData(el);
999+
const data = DomData.get(el);
10001000
const oldRAF = window.requestAnimationFrame;
10011001
const oldCAF = window.cancelAnimationFrame;
10021002

test/unit/menu.test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* eslint-env qunit */
2-
import * as DomData from '../../src/js/utils/dom-data';
2+
import DomData from '../../src/js/utils/dom-data';
33
import MenuButton from '../../src/js/menu/menu-button.js';
44
import Menu from '../../src/js/menu/menu.js';
55
import CaptionSettingsMenuItem from '../../src/js/control-bar/text-track-controls/caption-settings-menu-item';
@@ -137,7 +137,7 @@ QUnit.test('should remove old event listeners when the menu item adds to the new
137137
* A reusable collection of assertions.
138138
*/
139139
function validateMenuEventListeners(watchedMenu) {
140-
const eventData = DomData.getData(menuItem.eventBusEl_);
140+
const eventData = DomData.get(menuItem.eventBusEl_);
141141
// `MenuButton`.`unpressButton` will be called when triggering click event on the menu item.
142142
const unpressButtonSpy = sinon.spy(menuButton, 'unpressButton');
143143
// `MenuButton`.`focus` will be called when triggering click event on the menu item.

test/unit/utils/dom-data.test.js

Lines changed: 0 additions & 63 deletions
This file was deleted.

test/unit/videojs-integration.test.js

Lines changed: 0 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
import videojs from '../../src/js/video.js';
33
import window from 'global/window';
44
import document from 'global/document';
5-
import * as DomData from '../../src/js/utils/dom-data';
65
import * as Fn from '../../src/js/utils/fn';
76

87
/**
@@ -70,54 +69,11 @@ QUnit.test('create a real player and dispose', function(assert) {
7069

7170
player.muted(true);
7271

73-
const checkDomData = function() {
74-
Object.keys(DomData.elData).forEach(function(elId) {
75-
const data = DomData.elData[elId] || {};
76-
77-
Object.keys(data.handlers || {}).forEach(function(eventName) {
78-
const listeners = data.handlers[eventName];
79-
const uniqueList = [];
80-
81-
(listeners || []).forEach(function(listener) {
82-
let add = true;
83-
84-
for (let i = 0; i < uniqueList.length; i++) {
85-
const obj = uniqueList[i];
86-
87-
if (listener.og_ && listener.cx_ && obj.fn === listener.og_ && obj.cx === listener.cx_) {
88-
add = false;
89-
break;
90-
}
91-
92-
if (listener.og_ && !listener.cx_ && obj.fn === listener.og_) {
93-
add = false;
94-
break;
95-
}
96-
97-
if (!listener.og_ && !listener.cx_ && obj.fn === listener) {
98-
add = false;
99-
break;
100-
}
101-
}
102-
const obj = {fn: listener.og_ || listener, cx: listener.cx_};
103-
104-
if (add) {
105-
uniqueList.push(obj);
106-
assert.ok(true, `${elId}/${eventName}/${obj.fn.name} is unique`);
107-
} else {
108-
assert.ok(false, `${elId}/${eventName}/${obj.fn.name} is not unique`);
109-
}
110-
});
111-
});
112-
});
113-
};
114-
11572
player.addTextTrack('captions', 'foo', 'en');
11673
player.ready(function() {
11774
assert.ok(player.tech_, 'tech exists');
11875
assert.equal(player.textTracks().length, 1, 'should have one text track');
11976

120-
checkDomData();
12177
player.dispose();
12278

12379
Object.keys(old).forEach(function(k) {

0 commit comments

Comments
 (0)