Skip to content

Commit 04a6ea7

Browse files
committed
Re-use objects created by getData(): 50% speedup in expression evaluation
1 parent 07c0844 commit 04a6ea7

File tree

6 files changed

+86
-41
lines changed

6 files changed

+86
-41
lines changed

Diff for: src/collection.js

+19-11
Original file line numberDiff line numberDiff line change
@@ -68,22 +68,29 @@ var _ = Mavo.Collection = $.Class({
6868
var env = {
6969
context: this,
7070
options: o,
71-
data: []
71+
data: this.liveData
7272
};
7373

74-
this.children.forEach(item => {
74+
if (env.options.live) {
75+
this.proxyCache = {};
76+
}
77+
78+
for (var i = 0, j = 0; item = this.children[i]; i++) {
7579
if (!item.deleted || env.options.live) {
7680
var itemData = item.getData(env.options);
7781

7882
if (env.options.live || Mavo.value(itemData) !== null) {
79-
env.data.push(itemData);
83+
env.data[j] = itemData;
84+
j++;
8085
}
8186
}
82-
});
87+
}
88+
89+
env.data.length = j;
8390

8491
if (!this.mutable) {
8592
// If immutable, drop nulls
86-
env.data = env.data.filter(item => Mavo.value(item) !== null);
93+
Mavo.filter(env.data, item => Mavo.value(item) !== null);
8794

8895
if (env.options.live && env.data.length === 1) {
8996
// If immutable with only 1 item, return the item
@@ -96,18 +103,13 @@ var _ = Mavo.Collection = $.Class({
96103
}
97104
}
98105

99-
if (env.options.live && Array.isArray(env.data)) {
100-
env.data[Mavo.toNode] = this;
101-
env.data = this.relativizeData(env.data);
102-
}
103-
104106
if (!env.options.live) {
105107
env.data = Mavo.subset(this.data, this.inPath, env.data);
106108
}
107109

108110
Mavo.hooks.run("node-getdata-end", env);
109111

110-
return env.data;
112+
return (env.options.live? env.data[Mavo.toProxy] : env.data) || env.data;
111113
},
112114

113115
// Create item but don't insert it anywhere
@@ -437,6 +439,8 @@ var _ = Mavo.Collection = $.Class({
437439
}
438440
}
439441
}
442+
443+
this.createLiveData(data|| []);
440444
},
441445

442446
find: function(property, o = {}) {
@@ -619,6 +623,10 @@ var _ = Mavo.Collection = $.Class({
619623
});
620624

621625
return button;
626+
},
627+
628+
liveData: function() {
629+
return this.createLiveData([]);
622630
}
623631
},
624632

Diff for: src/group.js

+24-17
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ var _ = Mavo.Group = $.Class({
4444
}
4545
});
4646

47+
this.childrenNames = Object.keys(this.children);
48+
4749
var vocabElement = (this.isRoot? this.element.closest("[vocab]") : null) || this.element;
4850
this.vocab = vocabElement.getAttribute("vocab");
4951

@@ -72,11 +74,9 @@ var _ = Mavo.Group = $.Class({
7274
return env.data;
7375
}
7476

75-
env.data = (this.data? Mavo.clone(Mavo.subset(this.data, this.inPath)) : {}) || {};
76-
77-
var properties = Object.keys(this.children);
77+
env.data = this.liveData;
7878

79-
if (properties.length == 1 && properties[0] == this.property) {
79+
if (this.childrenNames.length == 1 && this.childrenNames[0] == this.property) {
8080
// {foo: {foo: 5}} should become {foo: 5}
8181
var options = $.extend($.extend({}, env.options), {forceObjects: true});
8282
env.data = this.children[this.property].getData(options);
@@ -87,22 +87,25 @@ var _ = Mavo.Group = $.Class({
8787

8888
if (obj.saved || env.options.live) {
8989
var data = obj.getData(env.options);
90+
}
9091

91-
if (data === null && !env.options.live) {
92-
delete env.data[obj.property];
93-
}
94-
else {
95-
env.data[obj.property] = data;
96-
}
92+
if (env.options.live || obj.saved && Mavo.value(data) !== null) {
93+
env.data[obj.property] = data;
94+
}
95+
else {
96+
delete env.data[obj.property];
9797
}
9898
}
9999
}
100100

101-
if (!env.options.live) { // Stored data again
101+
if (env.options.live) {
102+
this.proxyCache = {};
103+
}
104+
else { // Stored data again
102105
// If storing, use the rendered data too
103106
env.data = Mavo.subset(this.data, this.inPath, env.data);
104107

105-
if (!properties.length && !this.isRoot) {
108+
if (!this.childrenNames.length && !this.isRoot) {
106109
// Avoid {} in the data
107110
env.data = null;
108111
}
@@ -117,14 +120,10 @@ var _ = Mavo.Group = $.Class({
117120
}
118121
}
119122
}
120-
else if (env.data) {
121-
env.data[Mavo.toNode] = this;
122-
env.data = this.relativizeData(env.data);
123-
}
124123

125124
Mavo.hooks.run("node-getdata-end", env);
126125

127-
return env.data;
126+
return (env.options.live? env.data[Mavo.toProxy] : env.data) || env.data;
128127
},
129128

130129
/**
@@ -251,6 +250,14 @@ var _ = Mavo.Group = $.Class({
251250
}
252251
}
253252
}
253+
254+
this.createLiveData(data);
255+
},
256+
257+
lazy: {
258+
liveData: function() {
259+
return this.createLiveData();
260+
}
254261
},
255262

256263
static: {

Diff for: src/mavo.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -683,7 +683,8 @@ var _ = self.Mavo = $.Class({
683683

684684
lazy: {
685685
locale: () => document.documentElement.lang || "en-GB",
686-
toNode: () => Symbol("toNode")
686+
toNode: () => Symbol("toNode"),
687+
toProxy: () => Symbol("toProxy")
687688
}
688689
}
689690
});

Diff for: src/node.js

+20-11
Original file line numberDiff line numberDiff line change
@@ -363,22 +363,20 @@ var _ = Mavo.Node = $.Class({
363363
},
364364

365365
relativizeData: self.Proxy? function(data, options = {live: true}) {
366-
var cache = {};
367-
368366
return new Proxy(data, {
369367
get: (data, property, proxy) => {
370368
if (property in data) {
371369
return data[property];
372370
}
373371

374372
// Checking if property is in proxy might add it to the cache
375-
if (property in proxy && property in cache) {
376-
return cache[property];
373+
if (property in proxy && property in this.proxyCache) {
374+
return this.proxyCache[property];
377375
}
378376
},
379377

380378
has: (data, property) => {
381-
if (property in data || property in cache) {
379+
if (property in data || property in this.proxyCache) {
382380
return true;
383381
}
384382

@@ -387,16 +385,16 @@ var _ = Mavo.Node = $.Class({
387385
// Special values
388386
switch (property) {
389387
case "$index":
390-
cache[property] = this.index || 0;
388+
this.proxyCache[property] = this.index || 0;
391389
return true; // if index is 0 it's falsy and has would return false!
392390
case "$next":
393391
case "$previous":
394392
if (this.closestCollection) {
395-
cache[property] = this.closestCollection.getData(options)[this.index + (property == "$next"? 1 : -1)];
393+
this.proxyCache[property] = this.closestCollection.getData(options)[this.index + (property == "$next"? 1 : -1)];
396394
return true;
397395
}
398396

399-
cache[property] = null;
397+
this.proxyCache[property] = null;
400398
return false;
401399
}
402400

@@ -412,14 +410,14 @@ var _ = Mavo.Node = $.Class({
412410
ret = ret.getData(options);
413411
}
414412

415-
cache[property] = ret;
413+
this.proxyCache[property] = ret;
416414

417415
return true;
418416
}
419417

420418
// Does it reference another Mavo?
421-
if (property in Mavo.all && Mavo.all[property].root) {
422-
return cache[property] = Mavo.all[property].root.getData(options);
419+
if (property in Mavo.all && isNaN(property) && Mavo.all[property].root) {
420+
return this.proxyCache[property] = Mavo.all[property].root.getData(options);
423421
}
424422

425423
return false;
@@ -432,6 +430,13 @@ var _ = Mavo.Node = $.Class({
432430
});
433431
} : data => data,
434432

433+
createLiveData: function(obj = {}) {
434+
this.liveData = obj;
435+
this.liveData[Mavo.toNode] = this;
436+
this.liveData[Mavo.toProxy] = this.relativizeData(this.liveData);
437+
return this.liveData;
438+
},
439+
435440
pathFrom: function(node) {
436441
var path = this.path;
437442
var nodePath = node.path;
@@ -537,6 +542,10 @@ var _ = Mavo.Node = $.Class({
537542
}
538543

539544
return ret;
545+
},
546+
547+
proxyCache: function() {
548+
return {};
540549
}
541550
},
542551

Diff for: src/primitive.js

+8-1
Original file line numberDiff line numberDiff line change
@@ -219,12 +219,19 @@ var _ = Mavo.Primitive = $.Class({
219219
[Mavo.toNode]: this
220220
});
221221

222+
env.data[Mavo.toProxy] = this.relativizeData(env.data);
223+
222224
if (this.collection) {
223225
// Turn primitive collection items into objects, so we can have $index etc, and their property
224226
// name etc resolve relative to them, not their parent group
225227
env.data[this.property] = env.data;
226-
env.data = this.relativizeData(env.data);
227228
}
229+
230+
Mavo.hooks.run("node-getdata-end", env);
231+
232+
this.proxyCache = {};
233+
234+
return env.data[Mavo.toProxy];
228235
}
229236
}
230237
else if (this.inPath.length) {

Diff for: src/util.js

+13
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@ var _ = $.extend(Mavo, {
106106
return arr === undefined? [] : Array.isArray(arr)? arr : [arr];
107107
},
108108

109+
// Delete an element from an array
110+
// @param all {Boolean} Delete more than one?
109111
delete: (arr, element, all) => {
110112
do {
111113
var index = arr && arr.indexOf(element);
@@ -136,6 +138,17 @@ var _ = $.extend(Mavo, {
136138
return new Set([...(set1 || []), ...(set2 || [])]);
137139
},
138140

141+
// Filter an array in place
142+
// TODO add index to callback
143+
filter: (arr, callback) => {
144+
for (var i=0; i<arr.length; i++) {
145+
if (!callback(arr[i])) {
146+
arr.splice(i, 1);
147+
i--;
148+
}
149+
}
150+
},
151+
139152
/**
140153
* DOM element utilities
141154
*/

0 commit comments

Comments
 (0)