Skip to content

Commit

Permalink
micro-optimizations: (1) favor mixin over extends where possible, (2)…
Browse files Browse the repository at this point in the history
… unroll behavior lifecycle calls, (3) avoid creating a custom constructor when not used, (4) provide `_skipDefineProperty` setting on behaviors which copies properties via assignment rather than `copyOwnProperty`
  • Loading branch information
Steven Orvell committed Jan 25, 2017
1 parent 7e732f6 commit a1c1285
Show file tree
Hide file tree
Showing 15 changed files with 134 additions and 47 deletions.
4 changes: 3 additions & 1 deletion polymer.html
Expand Up @@ -29,7 +29,9 @@
// identity
this._prepIs();
// factory
this._prepConstructor();
if (this.factoryImpl) {
this._prepConstructor();
}
// styles
this._prepStyles();
},
Expand Down
76 changes: 65 additions & 11 deletions src/lib/base.html
Expand Up @@ -25,18 +25,18 @@
// pluggable features
// `this` context is a prototype, not an instance
_addFeature: function(feature) {
this.extend(this, feature);
this.mixin(this, feature);
},

// `this` context is a prototype, not an instance
registerCallback: function() {
/*
When lazyRegister is 'max' defer all behavior work until first element
creation.
When set, a behavior cannot setup an element's `is` or
creation.
When set, a behavior cannot setup an element's `is` or
custom constructor via defining `factoryImpl`.
We do call beforeRegister on the prototype to preserve
the ability to use it in ES6. This orders the element
the ability to use it in ES6. This orders the element
prototype's `beforeRegister` before behaviors' rather than after
as in the normal case.
*/
Expand All @@ -46,7 +46,15 @@
}
} else {
this._desugarBehaviors(); // abstract
this._doBehavior('beforeRegister'); // abstract
for (var i=0, b; i < this.behaviors.length; i++) {
b = this.behaviors[i];
if (b.beforeRegister) {
b.beforeRegister.call(this);
}
}
if (this.beforeRegister) {
this.beforeRegister();
}
}
this._registerFeatures(); // abstract
if (!settings.lazyRegister) {
Expand All @@ -60,7 +68,15 @@
}
Polymer.telemetry.instanceCount++;
this.root = this;
this._doBehavior('created'); // abstract
for (var i=0, b; i < this.behaviors.length; i++) {
b = this.behaviors[i];
if (b.created) {
b.created.call(this);
}
}
if (this.created) {
this.created();
}
this._initFeatures(); // abstract
},

Expand All @@ -81,14 +97,27 @@
// IFF `lazyRegister` is 'max'
if (settings.lazyRegister === 'max') {
proto._desugarBehaviors(); // abstract
proto._doBehaviorOnly('beforeRegister'); // abstract
for (var i=0, b; i < this.behaviors.length; i++) {
b = this.behaviors[i];
if (b.beforeRegister) {
b.beforeRegister.call(proto);
}
}
}
proto.__hasRegisterFinished = proto.is;
if (proto._finishRegisterFeatures) {
proto._finishRegisterFeatures();
}
// registration extension point
proto._doBehavior('registered');
for (var j=0, pb; j < proto.behaviors.length; j++) {
pb = proto.behaviors[j];
if (pb.registered) {
pb.registered.call(proto);
}
}
if (proto.registered) {
proto.registered();
}
// where prototypes are simulated (IE10), element instance
// must be specfically fixed up.
if (settings.usePolyfillProto && proto !== this) {
Expand All @@ -106,7 +135,15 @@
var self = this;
Polymer.RenderStatus.whenReady(function() {
self.isAttached = true;
self._doBehavior('attached'); // abstract
for (var i=0, b; i < self.behaviors.length; i++) {
b = self.behaviors[i];
if (b.attached) {
b.attached.call(self);
}
}
if (self.attached) {
self.attached();
}
});
},

Expand All @@ -116,7 +153,15 @@
var self = this;
Polymer.RenderStatus.whenReady(function() {
self.isAttached = false;
self._doBehavior('detached'); // abstract
for (var i=0, b; i < self.behaviors.length; i++) {
b = self.behaviors[i];
if (b.detached) {
b.detached.call(self);
}
}
if (self.detached) {
self.detached();
}
});
},

Expand All @@ -125,7 +170,15 @@
// TODO(sorvell): consider filtering out changes to host attributes
// note: this was barely measurable with 3 host attributes.
this._attributeChangedImpl(name); // abstract
this._doBehavior('attributeChanged', [name, oldValue, newValue]); // abstract
for (var i=0, b; i < this.behaviors.length; i++) {
b = this.behaviors[i];
if (b.attributeChanged) {
b.attributeChanged.call(this, name, oldValue, newValue);
}
}
if (this.attributeChanged) {
this.attributeChanged(name, oldValue, newValue);
}
},

_attributeChangedImpl: function(name) {
Expand Down Expand Up @@ -226,6 +279,7 @@
};

Polymer.Base = Polymer.Base.chainObject(Polymer.Base, HTMLElement.prototype);
Polymer.BaseDescriptors = {};

if (window.CustomElements) {
Polymer.instanceof = CustomElements.instanceof;
Expand Down
2 changes: 1 addition & 1 deletion src/lib/bind/effects.html
Expand Up @@ -12,7 +12,7 @@

<script>

Polymer.Base.extend(Polymer.Bind, {
Polymer.Base.mixin(Polymer.Bind, {

_shouldAddListener: function(effect) {
return effect.name &&
Expand Down
18 changes: 9 additions & 9 deletions src/lib/dom-api-distributed-nodes-observer.html
Expand Up @@ -16,7 +16,7 @@
var Settings = Polymer.Settings;

/**
* DomApi.DistributedNodesObserver notifies when the list returned by
* DomApi.DistributedNodesObserver notifies when the list returned by
* a <content> element's `getDistributedNodes()` may have changed.
* It is not meant to be used directly; it is used by
* `Polymer.dom(node).observeNodes(callback)` to observe changes to
Expand All @@ -26,10 +26,10 @@
DomApi.EffectiveNodesObserver.call(this, domApi);
};

DomApi.DistributedNodesObserver.prototype =
DomApi.DistributedNodesObserver.prototype =
Object.create(DomApi.EffectiveNodesObserver.prototype);

Polymer.Base.extend(DomApi.DistributedNodesObserver.prototype, {
Polymer.Base.mixin(DomApi.DistributedNodesObserver.prototype, {

// NOTE: ShadyDOM distribute provokes notification of these observers
// so no setup is required.
Expand All @@ -48,9 +48,9 @@
});

if (Settings.useShadow) {
Polymer.Base.extend(DomApi.DistributedNodesObserver.prototype, {

Polymer.Base.mixin(DomApi.DistributedNodesObserver.prototype, {

// NOTE: Under ShadowDOM we must observe the host element for
// changes.
_setup: function() {
Expand All @@ -63,8 +63,8 @@
self._scheduleNotify();
});
// NOTE: we identify this listener as needed for <content>
// notification so that enableShadowAttributeTracking
// can find these observers an ensure that we pass always
// notification so that enableShadowAttributeTracking
// can find these observers an ensure that we pass always
// pass notifications down.
this._observer._isContentListener = true;
if (this._hasAttrSelect()) {
Expand All @@ -89,7 +89,7 @@
}

});

}

})();
Expand Down
2 changes: 1 addition & 1 deletion src/lib/dom-api-effective-nodes-observer.html
Expand Up @@ -188,7 +188,7 @@
var baseSetup = DomApi.EffectiveNodesObserver.prototype._setup;
var baseCleanup = DomApi.EffectiveNodesObserver.prototype._cleanup;

Polymer.Base.extend(DomApi.EffectiveNodesObserver.prototype, {
Polymer.Base.mixin(DomApi.EffectiveNodesObserver.prototype, {

_setup: function() {
if (!this._observer) {
Expand Down
2 changes: 1 addition & 1 deletion src/lib/dom-api-flush.html
Expand Up @@ -17,7 +17,7 @@
* an element has to measure itself and is unsure about the state of its
* internal or compoased DOM.
*/
Polymer.Base.extend(Polymer.dom, {
Polymer.Base.mixin(Polymer.dom, {

_flushGuard: 0,
_FLUSH_MAX: 100,
Expand Down
2 changes: 1 addition & 1 deletion src/lib/dom-api-shadow.html
Expand Up @@ -20,7 +20,7 @@
return;
}

Polymer.Base.extend(DomApi.prototype, {
Polymer.Base.mixin(DomApi.prototype, {

querySelectorAll: function(selector) {
return TreeApi.arrayCopy(this.node.querySelectorAll(selector));
Expand Down
2 changes: 1 addition & 1 deletion src/lib/dom-api-shady.html
Expand Up @@ -28,7 +28,7 @@
var nativeCloneNode = Element.prototype.cloneNode;
var nativeImportNode = Document.prototype.importNode;

Polymer.Base.extend(DomApi.prototype, {
Polymer.Base.mixin(DomApi.prototype, {

_lazyDistribute: function(host) {
// note: only try to distribute if the root is not clean; this ensures
Expand Down
2 changes: 1 addition & 1 deletion src/lib/dom-module.html
Expand Up @@ -29,7 +29,7 @@

DomModule.prototype = Object.create(HTMLElement.prototype);

Polymer.Base.extend(DomModule.prototype, {
Polymer.Base.mixin(DomModule.prototype, {

constructor: DomModule,

Expand Down
11 changes: 7 additions & 4 deletions src/lib/polymer-bootstrap.html
Expand Up @@ -33,18 +33,18 @@
var factory = desugar(prototype);
// Polymer.Base is now chained to factory.prototype, and for IE10 compat
// this may have resulted in a new prototype being created
prototype = factory.prototype;
//prototype = factory.prototype;
var options = {
prototype: prototype
};
// NOTE: we're specifically supporting older Chrome versions here
// NOTE: we're specifically supporting older Chrome versions here
// (specifically Chrome 39) that throw when options.extends is undefined.
if (prototype.extends) {
options.extends = prototype.extends;
}
Polymer.telemetry._registrate(prototype);
document.registerElement(prototype.is, options);
return factory;
var ctor = document.registerElement(prototype.is, options);
return factory.__custom ? factory : ctor;
};

var desugar = function(prototype) {
Expand All @@ -56,7 +56,10 @@
base = Polymer.Base._getExtendedPrototype(prototype.extends);
}
prototype = Polymer.Base.chainObject(prototype, base);
var ctor = prototype.constructor;
prototype.registerCallback();
var custom = ctor !== prototype.constructor;
prototype.constructor.__custom = custom;
return prototype.constructor;
};

Expand Down
10 changes: 8 additions & 2 deletions src/micro/behaviors.html
Expand Up @@ -101,9 +101,14 @@

_mixinBehavior: function(b) {
var n$ = Object.getOwnPropertyNames(b);
var skipDefine = b._skipDefineProperty;
for (var i=0, n; (i<n$.length) && (n=n$[i]); i++) {
if (!Polymer.Base._behaviorProperties[n] && !this.hasOwnProperty(n)) {
this.copyOwnProperty(n, b, this);
if (skipDefine) {
this[n] = b[n];
} else {
this.copyOwnProperty(n, b, this);
}
}
}
},
Expand Down Expand Up @@ -167,7 +172,8 @@
attached: true,
detached: true,
attributeChanged: true,
ready: true
ready: true,
_skipDefineProperty: true
}

</script>
10 changes: 8 additions & 2 deletions src/micro/extends.html
Expand Up @@ -56,8 +56,14 @@
_getExtendedNativePrototype: function(tag) {
var p = this._nativePrototypes[tag];
if (!p) {
var np = this.getNativePrototype(tag);
p = this.extend(Object.create(np), Polymer.Base);
p = Object.create(this.getNativePrototype(tag));
var p$ = Object.getOwnPropertyNames(Polymer.Base);
for (var i=0, n; (i < p$.length) && (n=p$[i]); i++) {
if (!Polymer.BaseDescriptors[n]) {
p[n] = Polymer.Base[n];
}
}
Object.defineProperties(p, Polymer.BaseDescriptors);
this._nativePrototypes[tag] = p;
}
return p;
Expand Down
13 changes: 11 additions & 2 deletions src/mini/ready.html
Expand Up @@ -64,7 +64,7 @@
// never changes
_registerHost: function(host) {
// NOTE: The `dataHost` of an element never changes.
this.dataHost = host = host ||
this.dataHost = host = host ||
Polymer.Base._hostStack[Polymer.Base._hostStack.length-1];
if (host && host._clients) {
host._clients.push(this);
Expand Down Expand Up @@ -136,7 +136,16 @@
// mark readied and call `ready`
// note: called localChildren -> host
_readySelf: function() {
this._doBehavior('ready');
// ready
for (var i=0, b; i < this.behaviors.length; i++) {
b = this.behaviors[i];
if (b.ready) {
b.ready.call(this);
}
}
if (this.ready) {
this.ready();
}
this._readied = true;
if (this._attachedPending) {
this._attachedPending = false;
Expand Down

0 comments on commit a1c1285

Please sign in to comment.