Finally found a better and faster alternative for this.base() calls, by setting base on the function object instead.

base can be accessed on named functions very easily, leading to another measurable speed increase. Finally all performance reasons against straps.js are eliminated!
This commit is contained in:
Jürg Lehni 2013-05-27 10:04:05 -07:00
parent 369b329b23
commit c533dda7b5
13 changed files with 55 additions and 79 deletions

View file

@ -109,58 +109,34 @@ var Base = this.Base = new function() { // Straps scope
// string values starting with '#'
if (typeof val === 'string' && val[0] === '#')
val = src[val.substring(1)] || val;
var func = typeof val === 'function',
var isFunc = typeof val === 'function',
res = val,
// Only lookup previous value if we preserve or define a
// function that might need it for this.base(). If we're
// defining a getter, don't lookup previous value, but look if
// the property exists (name in dest) and store result in prev
prev = preserve || func
prev = preserve || isFunc
? (val && val.get ? name in dest : dest[name]) : null;
if ((dontCheck || val !== undefined && src.hasOwnProperty(name))
&& (!preserve || !prev)) {
if (func) {
if (prev && /\bthis\.base\b/.test(val)) {
var fromBase = base && base[name] == prev;
res = function() {
// Look up the base function each time if we can,
// to reflect changes to the base class after
// inheritance.
var tmp = describe(this, 'base');
define(this, 'base', { value: fromBase
? base[name] : prev, configurable: true });
try {
return val.apply(this, arguments);
} finally {
tmp ? define(this, 'base', tmp)
: delete this.base;
}
};
// Make wrapping closure pretend to be the original
// function on inspection
res.toString = function() {
return val.toString();
};
res.valueOf = function() {
return val.valueOf();
};
}
// Produce bean properties if getters are specified. This
// does not produce properties for setter-only properties.
// Just collect beans for now, and look them up in dest at
// the end of fields injection. This ensures this.base()
// works in beans too, and inherits setters for redefined
// getters in subclasses. Only add getter beans if they do
// not expect arguments. Functions that should function both
// with optional arguments and as beans should not declare
// the parameters and use the arguments array internally
// instead.
if (beans && val.length === 0
&& (bean = name.match(/^(get|is)(([A-Z])(.*))$/)))
beans.push([ bean[3].toLowerCase() + bean[4], bean[2] ]);
}
// Expose the 'super' function (meaning the one this function is
// overriding) through #base:
if (isFunc && prev)
val.base = prev;
// Produce bean properties if getters are specified. This does
// not produce properties for setter-only properties. Just
// collect beans for now, and look them up in dest at the end of
// fields injection. This ensures base works for beans too, and
// inherits setters for redefined getters in subclasses. Only
// add getter beans if they do not expect arguments. Functions
// that should function both with optional arguments and as
// beans should not declare the parameters and use the arguments
// array internally instead.
if (isFunc && beans && val.length === 0
&& (bean = name.match(/^(get|is)(([A-Z])(.*))$/)))
beans.push([ bean[3].toLowerCase() + bean[4], bean[2] ]);
// No need to look up getter if this is a function already.
if (!res || func || !res.get)
if (!res || isFunc || !res.get)
res = { value: res, writable: true };
// Only set/change configurable and enumerable if this field is
// configurable
@ -171,7 +147,7 @@ var Base = this.Base = new function() { // Straps scope
}
define(dest, name, res);
}
if (generics && func && (!preserve || !generics[name])) {
if (generics && isFunc && (!preserve || !generics[name])) {
generics[name] = function(bind) {
// Do not call Array.slice generic here, as on Safari,
// this seems to confuse scopes (calling another

View file

@ -82,11 +82,11 @@ this.Base = Base.inject(/** @lends Base# */{
_classes: {},
extend: function(src) {
extend: function extend(src) {
// Override Base.extend() with a version that registers classes that
// define #_class inside the Base._classes lookup, for
// deserialization.
var res = this.base.apply(this, arguments);
var res = extend.base.apply(this, arguments);
if (src._class)
Base._classes[src._class] = res;
return res;

View file

@ -98,7 +98,7 @@ var Callback = {
statics: {
// Override inject() so that sub-classes automatically add the accessors
// for the event handler functions (e.g. #onMouseDown) for each property
inject: function(/* src, ... */) {
inject: function inject(/* src, ... */) {
for (var i = 0, l = arguments.length; i < l; i++) {
var src = arguments[i],
events = src._events;
@ -132,7 +132,7 @@ var Callback = {
});
src._eventTypes = types;
}
this.base(src);
inject.base.call(this, src);
}
return this;
}

View file

@ -97,9 +97,8 @@ var Group = this.Group = Item.extend(/** @lends Group# */{
this.addChildren(Array.isArray(arg) ? arg : arguments);
},
_changed: function(flags) {
// Don't use this.base() for reasons of performance.
Item.prototype._changed.call(this, flags);
_changed: function _changed(flags) {
_changed.base.call(this, flags);
if (flags & (/*#=*/ ChangeFlag.HIERARCHY | /*#=*/ ChangeFlag.CLIPPING)) {
// Clear cached clip item whenever hierarchy changes
delete this._clipItem;

View file

@ -26,14 +26,14 @@ var Item = this.Item = Base.extend(Callback, /** @lends Item# */{
* Override Item.extend() to merge the subclass' _serializeFields with
* the parent class' _serializeFields.
*/
extend: function(src) {
extend: function extend(src) {
if (src._serializeFields)
src._serializeFields = Base.merge(
this.prototype._serializeFields, src._serializeFields);
// Derive the _type string from _class
if (src._class)
src._type = Base.hyphenate(src._class);
return this.base.apply(this, arguments);
return extend.base.apply(this, arguments);
}
},

View file

@ -69,9 +69,9 @@ var Layer = this.Layer = Group.extend(/** @lends Layer# */{
* Removes the layer from its project's layers list
* or its parent's children list.
*/
_remove: function(notify) {
_remove: function _remove(notify) {
if (this._parent)
return this.base(notify);
return _remove.base.call(this, notify);
if (this._index != null) {
if (this._project.activeLayer === this)
this._project.activeLayer = this.getNextSibling()
@ -85,18 +85,18 @@ var Layer = this.Layer = Group.extend(/** @lends Layer# */{
return false;
},
getNextSibling: function() {
return this._parent ? this.base()
getNextSibling: function getNextSibling() {
return this._parent ? getNextSibling.base.call(this)
: this._project.layers[this._index + 1] || null;
},
getPreviousSibling: function() {
return this._parent ? this.base()
getPreviousSibling: function getPreviousSibling() {
return this._parent ? getPreviousSibling.base.call(this)
: this._project.layers[this._index - 1] || null;
},
isInserted: function() {
return this._parent ? this.base() : this._index != null;
isInserted: function isInserted() {
return this._parent ? isInserted.base.call(this) : this._index != null;
},
/**
@ -114,7 +114,7 @@ var Layer = this.Layer = Group.extend(/** @lends Layer# */{
}
}, new function () {
function insert(above) {
return function(item) {
return function insert(item) {
// If the item is a layer and contained within Project#layers, use
// our own version of move().
if (item instanceof Layer && !item._parent
@ -124,7 +124,7 @@ var Layer = this.Layer = Group.extend(/** @lends Layer# */{
this._setProject(item._project);
return true;
}
return this.base(item);
return insert.base.call(this, item);
};
}

View file

@ -67,10 +67,10 @@ var Shape = this.Shape = Item.extend(/** @lends Shape# */{
}
},
_contains: function(point) {
_contains: function _contains(point) {
switch (this._type) {
case 'rect':
return this.base(point);
return _contains.base.call(this, point);
case 'circle':
case 'ellipse':
return point.divide(this._size).getLength() <= 0.5;

View file

@ -58,11 +58,11 @@ var CompoundPath = this.CompoundPath = PathItem.extend(/** @lends CompoundPath#
this.addChildren(Array.isArray(arg) ? arg : arguments);
},
insertChild: function(index, item, _preserve) {
insertChild: function insertChild(index, item, _preserve) {
// Only allow the insertion of paths
if (item._type !== 'path')
return null;
item = this.base(index, item);
item = insertChild.base.call(this, index, item);
// All children except for the bottom one (first one in list) are set
// to anti-clockwise orientation, so that they appear as holes, but
// only if their orientation was not already specified before
@ -223,8 +223,9 @@ var CompoundPath = this.CompoundPath = PathItem.extend(/** @lends CompoundPath#
return (children.length & 1) == 1 && children;
},
_hitTest: function(point, options) {
var res = this.base(point, Base.merge(options, { fill: false }));
_hitTest: function _hitTest(point, options) {
var res = _hitTest.base.call(this, point,
Base.merge(options, { fill: false }));
if (!res && options.fill && this._style.getFillColor()) {
res = this._contains(point);
res = res ? new HitResult('fill', res[0]) : null;

View file

@ -788,12 +788,12 @@ var Path = this.Path = PathItem.extend(/** @lends Path# */{
this.setSelected(selected);
},
setSelected: function(selected) {
setSelected: function setSelected(selected) {
// Deselect all segments when path is marked as not selected
if (!selected)
this._selectSegments(false);
// No need to pass true for noChildren since Path has none anyway.
this.base(selected);
setSelected.base.call(this, selected);
},
_selectSegments: function(selected) {

View file

@ -97,8 +97,8 @@ var Project = this.Project = PaperScopeItem.extend(/** @lends Project# */{
* Removes this project from the {@link PaperScope#projects} list, and also
* removes its view, if one was defined.
*/
remove: function() {
if (!this.base())
remove: function remove() {
if (!remove.base.call(this))
return false;
if (this.view)
this.view.remove();

View file

@ -223,9 +223,9 @@ var Style = this.Style = Base.extend(new function() {
}
},
getLeading: function() {
getLeading: function getLeading() {
// Override leading to return fontSize * 1.2 by default.
var leading = this.base();
var leading = getLeading.base.call(this);
return leading != null ? leading : this.getFontSize() * 1.2;
},

View file

@ -77,9 +77,9 @@ var TextItem = this.TextItem = Item.extend(/** @lends TextItem# */{
* }
*/
_clone: function(copy) {
_clone: function _clone(copy) {
copy.setContent(this._content);
return this.base(copy);
return _clone.base.call(this, copy);
},
getContent: function() {

View file

@ -322,7 +322,7 @@ var Tool = this.Tool = PaperScopeItem.extend(/** @lends Tool# */{
return true;
},
fire: function(type, event) {
fire: function fire(type, event) {
// Override Callback#fire() so we can handle items marked in removeOn*()
// calls first,.
var sets = Tool._removeSets;
@ -346,7 +346,7 @@ var Tool = this.Tool = PaperScopeItem.extend(/** @lends Tool# */{
sets[type] = null;
}
}
return this.base(type, event);
return fire.base.call(this, type, event);
},
_onHandleEvent: function(type, point, event) {