-
Notifications
You must be signed in to change notification settings - Fork 214
address preformance regression introduced in pr 845 #852
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -232,6 +232,8 @@ YUI.add('mojito-util', function(Y) { | |
* object containing the recursively merged values of the two. This is | ||
* similar to performing a copy and then a mergeRecursive operation but | ||
* is intended to be more performant by avoiding duplicate work. | ||
* | ||
* @method blend | ||
* @param {Object} base The base object. Properties in this object may | ||
* be overwritten by those in overlay. | ||
* @param {Object} extension The "overlay" object. Properties in this | ||
|
@@ -240,11 +242,9 @@ YUI.add('mojito-util', function(Y) { | |
*/ | ||
blend: function(base, overlay) { | ||
|
||
var tmp, | ||
dest, | ||
var dest, | ||
i, | ||
slot, | ||
slotType, | ||
copy = Y.mojito.util.copy, | ||
blend = Y.mojito.util.blend; | ||
|
||
|
@@ -257,14 +257,11 @@ YUI.add('mojito-util', function(Y) { | |
} | ||
|
||
if (Y.Lang.isArray(overlay)) { | ||
if (!Y.Lang.isArray(base)) { | ||
throw new Error('Type mismatch for object merge.'); | ||
if (Y.Lang.isArray(base)) { | ||
return Y.Array.unique(base.concat(overlay)); | ||
} else { | ||
return overlay.slice(0); | ||
} | ||
|
||
tmp = base.slice(0); | ||
tmp.push.apply(tmp, overlay); | ||
dest = Y.Array.unique(tmp); | ||
tmp.length = 0; | ||
} else { | ||
dest = {}; | ||
for (i in overlay) { | ||
|
@@ -273,14 +270,12 @@ YUI.add('mojito-util', function(Y) { | |
// "Conflicting" property. A recursive merge of the | ||
// value is required if it's a complex object. | ||
slot = overlay[i]; | ||
slotType = typeof slot; | ||
if ((slotType === 'object') && | ||
(slot !== null)) { | ||
if (Y.Lang.isObject(slot)) { | ||
// Complex, not null. | ||
dest[i] = blend(base[i], overlay[i]); | ||
dest[i] = blend(base[i], slot); | ||
} else { | ||
// Simple value or null. Overlay value wins. | ||
dest[i] = overlay[i]; | ||
dest[i] = slot; | ||
} | ||
} else { | ||
// Unique to the overlay object, no merge needed. | ||
|
@@ -293,14 +288,127 @@ YUI.add('mojito-util', function(Y) { | |
// missing from the destination object should be copied. | ||
// Anything already there would have been merged in the previous | ||
// loop. | ||
for (i in base) { | ||
if (base.hasOwnProperty(i)) { | ||
if (!dest.hasOwnProperty(i)) { | ||
dest[i] = copy(base[i]); | ||
if (Y.Lang.isObject(base) && !Y.Lang.isArray(base)) { | ||
for (i in base) { | ||
if (base.hasOwnProperty(i)) { | ||
if (!dest.hasOwnProperty(i)) { | ||
dest[i] = copy(base[i]); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
return dest; | ||
}, | ||
|
||
|
||
/** | ||
* Blends three objects to create a new object containing the | ||
* recursively merged values of the them all. This is similar to | ||
* performing copy()s and then mergeRecursive()s but is intended | ||
* to be more performant by avoiding duplicate work. | ||
* | ||
* The type of the "highest" parameter always dictates type of results. | ||
* | ||
* @method blend3 | ||
* @param {Object} lowest The lowest priority object. | ||
* Properties in this object may be overwritten by those in medium and highest. | ||
* @param {Object} medium The medium priority object. | ||
* Properties in this object may be overwritten by those in highest. | ||
* @param {Object} highest The highest priority object. | ||
* Properties in this object are always found in the results. | ||
* @return {Object} A new object containing the blended values. | ||
*/ | ||
blend3: function(lowest, medium, highest) { | ||
var dest, | ||
key, | ||
val, | ||
copy = Y.mojito.util.copy, | ||
blend2 = Y.mojito.util.blend, | ||
blend3 = Y.mojito.util.blend3; | ||
|
||
// Protect from bad input data. | ||
if (highest === null || highest === undefined) { | ||
return blend2(lowest, medium); | ||
} | ||
|
||
if (Y.Lang.isArray(highest)) { | ||
if (Y.Lang.isArray(medium)) { | ||
if (Y.Lang.isArray(lowest)) { | ||
dest = lowest.concat(medium, highest); | ||
} else { | ||
dest = medium.concat(highest); | ||
} | ||
} else { | ||
dest = highest.slice(0); | ||
} | ||
return Y.Array.unique(dest); | ||
} | ||
|
||
// Otherwise treat everything as an object. | ||
dest = {}; | ||
if ((!Y.Lang.isObject(medium)) || Y.Lang.isArray(medium)) { | ||
medium = null; | ||
lowest = null; | ||
} | ||
if ((!Y.Lang.isObject(lowest)) || Y.Lang.isArray(lowest)) { | ||
lowest = null; | ||
} | ||
for (key in highest) { | ||
if (highest.hasOwnProperty(key)) { | ||
val = highest[key]; | ||
if (Y.Lang.isObject(val)) { | ||
if (medium && medium.hasOwnProperty(key)) { | ||
if (lowest && lowest.hasOwnProperty(key)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Might be able to squeeze a bit more out of this by not checking medium and lowest repeatedly. I think these checks are going to do implicit Boolean conversions with each iteration. |
||
dest[key] = blend3(lowest[key], medium[key], val); | ||
} else { | ||
dest[key] = blend2(medium[key], val); | ||
} | ||
} else { | ||
if (lowest && lowest.hasOwnProperty(key)) { | ||
dest[key] = blend2(lowest[key], val); | ||
} else { | ||
dest[key] = copy(val); | ||
} | ||
} | ||
} else { | ||
dest[key] = copy(val); | ||
} | ||
} | ||
} | ||
|
||
// Process keys that are in medium but not in highest. | ||
if (medium) { | ||
for (key in medium) { | ||
if (medium.hasOwnProperty(key)) { | ||
if (!dest.hasOwnProperty(key)) { | ||
val = medium[key]; | ||
if (Y.Lang.isObject(val)) { | ||
if (lowest && lowest.hasOwnProperty(key)) { | ||
dest[key] = blend2(lowest[key], val); | ||
} else { | ||
dest[key] = copy(val); | ||
} | ||
} else { | ||
// scalar | ||
dest[key] = val; | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
// Process keys that are in lowest but not highest nor in medium. | ||
if (lowest) { | ||
for (key in lowest) { | ||
if (lowest.hasOwnProperty(key)) { | ||
if (!dest.hasOwnProperty(key)) { | ||
dest[key] = copy(lowest[key]); | ||
} | ||
} | ||
} | ||
} | ||
|
||
return dest; | ||
}, | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -212,6 +212,36 @@ YUI.add('mojito-test', function(Y, NAME) { | |
|
||
|
||
YUI.add('mojito-test-extra', function(Y, NAME) { | ||
var A = Y.Assert; | ||
|
||
Y.MOJITO_DIR = require ? | ||
require('path').resolve(__dirname, '../../') + '/' : null; | ||
}); | ||
|
||
// path doesn't need to be given, mainly used during recursion | ||
Y.TEST_CMP = function (x, y, msg, path) { | ||
path = path || 'obj'; | ||
var i; | ||
if (Y.Lang.isArray(x)) { | ||
A.isArray(x, path + ': ' + (msg || 'first arg should be an array')); | ||
A.isArray(y, path + ': ' + (msg || 'second arg should be an array')); | ||
A.areSame(x.length, y.length, path + ': ' + (msg || 'arrays are different lengths')); | ||
for (i = 0; i < x.length; i += 1) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Any reason you're not using ArrayAssert here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I copied this code from another file, so I'm not sure why. I think it's because ArrayAssert doesn't do a recursive comparison of values if the array contains things like objects or other arrays. |
||
Y.TEST_CMP(x[i], y[i], msg, path + '[' + i + ']'); | ||
} | ||
return; | ||
} | ||
if (Y.Lang.isObject(x)) { | ||
A.isObject(x, path + ': ' + (msg || 'first arg should be an object')); | ||
A.isObject(y, path + ': ' + (msg || 'second arg should be an object')); | ||
A.areSame(Object.keys(x).length, Object.keys(y).length, path + ': ' + (msg || 'object keys are different lengths')); | ||
for (i in x) { | ||
if (x.hasOwnProperty(i)) { | ||
Y.TEST_CMP(x[i], y[i], msg, path + '{' + i + '}'); | ||
} | ||
} | ||
return; | ||
} | ||
A.areSame(x, y, path + ': ' + (msg || 'args should be the same')); | ||
}; | ||
|
||
}, '0.1.0', {requires: ['test']}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is pretty risky because
getMojitTypeDetails
is a public method, and people will use it in many different ways, they might end up corrupting the cached data. Can we pass a flag or something if we don't want to make a copy intentionally to make it work more performant?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes I agree.
However, since in
expandInstanceForEnv()
we're using the results as part of ablend3()
the extra copy doesn't do anything, and removing it lead to a very noticeable performance gain.I'll add the flag.