Skip to content

Commit c533dda

Browse files
committed
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!
1 parent 369b329 commit c533dda

13 files changed

Lines changed: 55 additions & 79 deletions

File tree

lib/straps.js

Lines changed: 20 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -109,58 +109,34 @@ var Base = this.Base = new function() { // Straps scope
109109
// string values starting with '#'
110110
if (typeof val === 'string' && val[0] === '#')
111111
val = src[val.substring(1)] || val;
112-
var func = typeof val === 'function',
112+
var isFunc = typeof val === 'function',
113113
res = val,
114114
// Only lookup previous value if we preserve or define a
115115
// function that might need it for this.base(). If we're
116116
// defining a getter, don't lookup previous value, but look if
117117
// the property exists (name in dest) and store result in prev
118-
prev = preserve || func
118+
prev = preserve || isFunc
119119
? (val && val.get ? name in dest : dest[name]) : null;
120120
if ((dontCheck || val !== undefined && src.hasOwnProperty(name))
121121
&& (!preserve || !prev)) {
122-
if (func) {
123-
if (prev && /\bthis\.base\b/.test(val)) {
124-
var fromBase = base && base[name] == prev;
125-
res = function() {
126-
// Look up the base function each time if we can,
127-
// to reflect changes to the base class after
128-
// inheritance.
129-
var tmp = describe(this, 'base');
130-
define(this, 'base', { value: fromBase
131-
? base[name] : prev, configurable: true });
132-
try {
133-
return val.apply(this, arguments);
134-
} finally {
135-
tmp ? define(this, 'base', tmp)
136-
: delete this.base;
137-
}
138-
};
139-
// Make wrapping closure pretend to be the original
140-
// function on inspection
141-
res.toString = function() {
142-
return val.toString();
143-
};
144-
res.valueOf = function() {
145-
return val.valueOf();
146-
};
147-
}
148-
// Produce bean properties if getters are specified. This
149-
// does not produce properties for setter-only properties.
150-
// Just collect beans for now, and look them up in dest at
151-
// the end of fields injection. This ensures this.base()
152-
// works in beans too, and inherits setters for redefined
153-
// getters in subclasses. Only add getter beans if they do
154-
// not expect arguments. Functions that should function both
155-
// with optional arguments and as beans should not declare
156-
// the parameters and use the arguments array internally
157-
// instead.
158-
if (beans && val.length === 0
159-
&& (bean = name.match(/^(get|is)(([A-Z])(.*))$/)))
160-
beans.push([ bean[3].toLowerCase() + bean[4], bean[2] ]);
161-
}
122+
// Expose the 'super' function (meaning the one this function is
123+
// overriding) through #base:
124+
if (isFunc && prev)
125+
val.base = prev;
126+
// Produce bean properties if getters are specified. This does
127+
// not produce properties for setter-only properties. Just
128+
// collect beans for now, and look them up in dest at the end of
129+
// fields injection. This ensures base works for beans too, and
130+
// inherits setters for redefined getters in subclasses. Only
131+
// add getter beans if they do not expect arguments. Functions
132+
// that should function both with optional arguments and as
133+
// beans should not declare the parameters and use the arguments
134+
// array internally instead.
135+
if (isFunc && beans && val.length === 0
136+
&& (bean = name.match(/^(get|is)(([A-Z])(.*))$/)))
137+
beans.push([ bean[3].toLowerCase() + bean[4], bean[2] ]);
162138
// No need to look up getter if this is a function already.
163-
if (!res || func || !res.get)
139+
if (!res || isFunc || !res.get)
164140
res = { value: res, writable: true };
165141
// Only set/change configurable and enumerable if this field is
166142
// configurable
@@ -171,7 +147,7 @@ var Base = this.Base = new function() { // Straps scope
171147
}
172148
define(dest, name, res);
173149
}
174-
if (generics && func && (!preserve || !generics[name])) {
150+
if (generics && isFunc && (!preserve || !generics[name])) {
175151
generics[name] = function(bind) {
176152
// Do not call Array.slice generic here, as on Safari,
177153
// this seems to confuse scopes (calling another

src/core/Base.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,11 +82,11 @@ this.Base = Base.inject(/** @lends Base# */{
8282

8383
_classes: {},
8484

85-
extend: function(src) {
85+
extend: function extend(src) {
8686
// Override Base.extend() with a version that registers classes that
8787
// define #_class inside the Base._classes lookup, for
8888
// deserialization.
89-
var res = this.base.apply(this, arguments);
89+
var res = extend.base.apply(this, arguments);
9090
if (src._class)
9191
Base._classes[src._class] = res;
9292
return res;

src/core/Callback.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ var Callback = {
9898
statics: {
9999
// Override inject() so that sub-classes automatically add the accessors
100100
// for the event handler functions (e.g. #onMouseDown) for each property
101-
inject: function(/* src, ... */) {
101+
inject: function inject(/* src, ... */) {
102102
for (var i = 0, l = arguments.length; i < l; i++) {
103103
var src = arguments[i],
104104
events = src._events;
@@ -132,7 +132,7 @@ var Callback = {
132132
});
133133
src._eventTypes = types;
134134
}
135-
this.base(src);
135+
inject.base.call(this, src);
136136
}
137137
return this;
138138
}

src/item/Group.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,9 +97,8 @@ var Group = this.Group = Item.extend(/** @lends Group# */{
9797
this.addChildren(Array.isArray(arg) ? arg : arguments);
9898
},
9999

100-
_changed: function(flags) {
101-
// Don't use this.base() for reasons of performance.
102-
Item.prototype._changed.call(this, flags);
100+
_changed: function _changed(flags) {
101+
_changed.base.call(this, flags);
103102
if (flags & (/*#=*/ ChangeFlag.HIERARCHY | /*#=*/ ChangeFlag.CLIPPING)) {
104103
// Clear cached clip item whenever hierarchy changes
105104
delete this._clipItem;

src/item/Item.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,14 @@ var Item = this.Item = Base.extend(Callback, /** @lends Item# */{
2626
* Override Item.extend() to merge the subclass' _serializeFields with
2727
* the parent class' _serializeFields.
2828
*/
29-
extend: function(src) {
29+
extend: function extend(src) {
3030
if (src._serializeFields)
3131
src._serializeFields = Base.merge(
3232
this.prototype._serializeFields, src._serializeFields);
3333
// Derive the _type string from _class
3434
if (src._class)
3535
src._type = Base.hyphenate(src._class);
36-
return this.base.apply(this, arguments);
36+
return extend.base.apply(this, arguments);
3737
}
3838
},
3939

src/item/Layer.js

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,9 @@ var Layer = this.Layer = Group.extend(/** @lends Layer# */{
6969
* Removes the layer from its project's layers list
7070
* or its parent's children list.
7171
*/
72-
_remove: function(notify) {
72+
_remove: function _remove(notify) {
7373
if (this._parent)
74-
return this.base(notify);
74+
return _remove.base.call(this, notify);
7575
if (this._index != null) {
7676
if (this._project.activeLayer === this)
7777
this._project.activeLayer = this.getNextSibling()
@@ -85,18 +85,18 @@ var Layer = this.Layer = Group.extend(/** @lends Layer# */{
8585
return false;
8686
},
8787

88-
getNextSibling: function() {
89-
return this._parent ? this.base()
88+
getNextSibling: function getNextSibling() {
89+
return this._parent ? getNextSibling.base.call(this)
9090
: this._project.layers[this._index + 1] || null;
9191
},
9292

93-
getPreviousSibling: function() {
94-
return this._parent ? this.base()
93+
getPreviousSibling: function getPreviousSibling() {
94+
return this._parent ? getPreviousSibling.base.call(this)
9595
: this._project.layers[this._index - 1] || null;
9696
},
9797

98-
isInserted: function() {
99-
return this._parent ? this.base() : this._index != null;
98+
isInserted: function isInserted() {
99+
return this._parent ? isInserted.base.call(this) : this._index != null;
100100
},
101101

102102
/**
@@ -114,7 +114,7 @@ var Layer = this.Layer = Group.extend(/** @lends Layer# */{
114114
}
115115
}, new function () {
116116
function insert(above) {
117-
return function(item) {
117+
return function insert(item) {
118118
// If the item is a layer and contained within Project#layers, use
119119
// our own version of move().
120120
if (item instanceof Layer && !item._parent
@@ -124,7 +124,7 @@ var Layer = this.Layer = Group.extend(/** @lends Layer# */{
124124
this._setProject(item._project);
125125
return true;
126126
}
127-
return this.base(item);
127+
return insert.base.call(this, item);
128128
};
129129
}
130130

src/item/Shape.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,10 @@ var Shape = this.Shape = Item.extend(/** @lends Shape# */{
6767
}
6868
},
6969

70-
_contains: function(point) {
70+
_contains: function _contains(point) {
7171
switch (this._type) {
7272
case 'rect':
73-
return this.base(point);
73+
return _contains.base.call(this, point);
7474
case 'circle':
7575
case 'ellipse':
7676
return point.divide(this._size).getLength() <= 0.5;

src/path/CompoundPath.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,11 @@ var CompoundPath = this.CompoundPath = PathItem.extend(/** @lends CompoundPath#
5858
this.addChildren(Array.isArray(arg) ? arg : arguments);
5959
},
6060

61-
insertChild: function(index, item, _preserve) {
61+
insertChild: function insertChild(index, item, _preserve) {
6262
// Only allow the insertion of paths
6363
if (item._type !== 'path')
6464
return null;
65-
item = this.base(index, item);
65+
item = insertChild.base.call(this, index, item);
6666
// All children except for the bottom one (first one in list) are set
6767
// to anti-clockwise orientation, so that they appear as holes, but
6868
// only if their orientation was not already specified before
@@ -223,8 +223,9 @@ var CompoundPath = this.CompoundPath = PathItem.extend(/** @lends CompoundPath#
223223
return (children.length & 1) == 1 && children;
224224
},
225225

226-
_hitTest: function(point, options) {
227-
var res = this.base(point, Base.merge(options, { fill: false }));
226+
_hitTest: function _hitTest(point, options) {
227+
var res = _hitTest.base.call(this, point,
228+
Base.merge(options, { fill: false }));
228229
if (!res && options.fill && this._style.getFillColor()) {
229230
res = this._contains(point);
230231
res = res ? new HitResult('fill', res[0]) : null;

src/path/Path.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -788,12 +788,12 @@ var Path = this.Path = PathItem.extend(/** @lends Path# */{
788788
this.setSelected(selected);
789789
},
790790

791-
setSelected: function(selected) {
791+
setSelected: function setSelected(selected) {
792792
// Deselect all segments when path is marked as not selected
793793
if (!selected)
794794
this._selectSegments(false);
795795
// No need to pass true for noChildren since Path has none anyway.
796-
this.base(selected);
796+
setSelected.base.call(this, selected);
797797
},
798798

799799
_selectSegments: function(selected) {

src/project/Project.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,8 @@ var Project = this.Project = PaperScopeItem.extend(/** @lends Project# */{
9797
* Removes this project from the {@link PaperScope#projects} list, and also
9898
* removes its view, if one was defined.
9999
*/
100-
remove: function() {
101-
if (!this.base())
100+
remove: function remove() {
101+
if (!remove.base.call(this))
102102
return false;
103103
if (this.view)
104104
this.view.remove();

0 commit comments

Comments
 (0)