From c533dda7b59cf53e7e58016d907e34cfc9e283cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrg=20Lehni?= Date: Mon, 27 May 2013 10:04:05 -0700 Subject: [PATCH] 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! --- lib/straps.js | 64 +++++++++++++--------------------------- src/core/Base.js | 4 +-- src/core/Callback.js | 4 +-- src/item/Group.js | 5 ++-- src/item/Item.js | 4 +-- src/item/Layer.js | 20 ++++++------- src/item/Shape.js | 4 +-- src/path/CompoundPath.js | 9 +++--- src/path/Path.js | 4 +-- src/project/Project.js | 4 +-- src/style/Style.js | 4 +-- src/text/TextItem.js | 4 +-- src/tool/Tool.js | 4 +-- 13 files changed, 55 insertions(+), 79 deletions(-) diff --git a/lib/straps.js b/lib/straps.js index 3e638b0b..a4fa1b1a 100755 --- a/lib/straps.js +++ b/lib/straps.js @@ -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 diff --git a/src/core/Base.js b/src/core/Base.js index 6093ae47..98c1b588 100644 --- a/src/core/Base.js +++ b/src/core/Base.js @@ -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; diff --git a/src/core/Callback.js b/src/core/Callback.js index 55ccd429..676a04c4 100644 --- a/src/core/Callback.js +++ b/src/core/Callback.js @@ -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; } diff --git a/src/item/Group.js b/src/item/Group.js index f7558d09..7ca9d022 100644 --- a/src/item/Group.js +++ b/src/item/Group.js @@ -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; diff --git a/src/item/Item.js b/src/item/Item.js index d73aba66..a973a0a5 100644 --- a/src/item/Item.js +++ b/src/item/Item.js @@ -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); } }, diff --git a/src/item/Layer.js b/src/item/Layer.js index 8d60f91a..516376ab 100644 --- a/src/item/Layer.js +++ b/src/item/Layer.js @@ -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); }; } diff --git a/src/item/Shape.js b/src/item/Shape.js index 4320a82d..e4d6a3f0 100644 --- a/src/item/Shape.js +++ b/src/item/Shape.js @@ -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; diff --git a/src/path/CompoundPath.js b/src/path/CompoundPath.js index a35313a2..7e313dde 100644 --- a/src/path/CompoundPath.js +++ b/src/path/CompoundPath.js @@ -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; diff --git a/src/path/Path.js b/src/path/Path.js index 4850eb24..949427b7 100644 --- a/src/path/Path.js +++ b/src/path/Path.js @@ -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) { diff --git a/src/project/Project.js b/src/project/Project.js index bd1c1e5b..30221432 100644 --- a/src/project/Project.js +++ b/src/project/Project.js @@ -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(); diff --git a/src/style/Style.js b/src/style/Style.js index b965dbb8..0df9562d 100644 --- a/src/style/Style.js +++ b/src/style/Style.js @@ -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; }, diff --git a/src/text/TextItem.js b/src/text/TextItem.js index 81f132e3..5004b068 100644 --- a/src/text/TextItem.js +++ b/src/text/TextItem.js @@ -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() { diff --git a/src/tool/Tool.js b/src/tool/Tool.js index 8f227150..81e2374c 100644 --- a/src/tool/Tool.js +++ b/src/tool/Tool.js @@ -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) {