I noticed some redundancy in a script I ran through Google Closure Compiler.
(function(){function g(a){var k;if(a){if(a.call)a.prototype=j,a.prototype[e]={}}else a=
{};var c=a,b,c=(a=c.call?c:null)?new a(a):c;b=c[e]||{};var f=b.extend;b=b.a;var
d=c.hasOwnProperty("constructor")?c.constructor:b?b:f?g.b(f):new Function;if
(f)b=d.prototype,k=new h(h.prototype=f.prototype),f=k,h.prototype={},d.prototype=f,i
(d.prototype,b);i(d,a);i(d.prototype,c);return d.prototype.constructor=d}function i(a,c)
{for(var b in c)c.hasOwnProperty(b)&&"prototype"!=b&&b!=e&&(a[b]=c[b])}var h=new Function,
e="decl-data",j={extend:function(a){return(this[e].extend=a).prototype},a:function(a)
{return(this[e].a=a).prototype}};g.c=function(a){e=a};return g})().b=function(g){return
function(){g.apply(this,arguments)}};
It looks like a big sea of prototype and constructor, doesn't it?
I was was able to get it to about 6/7 of the size of the original by storing "constructor" and "prototype" strings and using array notation everywhere to access prototypes and constructors. The size savings should grow as the size of the script grows. Here's what it looks like after the change:
(function(b,h){function i(a){a?a.call&&(a[b]=l,a[b][f]={}):a={};var d=a,c,d=(a=d.call?
d:null)?new a(a):d;c=d[f]||{};var g=c.extend;c=c.a;var e=d.hasOwnProperty(h)?d[h]:c?c:g?i.b
(g):new Function;if(g){c=e[b];var m=b,g=new j(j[b]=g[b]);j[b]={};e[m]=g;k(e[b],c)}k(e,a);k(e
[b],d);return e[b][h]=e}function k(a,d){for(var c in d)d.hasOwnProperty(c)&&c!=b&&c!=f&&(a
[c]=d[c])}var j=new Function,f="decl-data",l={extend:function(a){return(this[f].extend=a)
[b]},a:function(a){return(this[f].a=a)[b]}};i.c=function(a){f=a};return i})
("prototype","constructor").b=function(b){return function(){b.apply(this,arguments)}};
Is it worth it to damage readability to save a few bytes? It's hard to know when to stop... hasOwnProperty only appears twice, I might use it once more. So, should I replace .hasOwnProperty with [_has]? Should I just stop here? Or should I put it back the way it was, and not worry about the few extra bytes?
On the other hand, maybe this doesn't damage readability at all, and could just be looked at as convenient shortcuts.
Another alternative would be abstracting the behavior using macros; write .prototype and have it converted to [_p] at build time, of course declaring _p somewhere. But that seems overly complicated for a relatively minor size optimization. Speaking of which, I wonder why closure-compiler doesn't do something like this already?
I'm curious what the community thinks about this.
For reference, the un-minified source before the size optimization:
var decl = (function(){
var Clone = new Function(), // dummy function for prototypal cloning
/** dataKey
The name of the property where declaration objects'
metadata will be stored. If you want to pass objects to decl
instead of functions, put the metadata (parent, partial, etc.)
in this property.
*/
dataKey = 'decl-data',
/** proto
This object is used as a prototype for declaration objects,
so all properties are available as properties of `this`
inside the body of each declaration function.
*/
proto = {
/** extend
Perform prototypal inheritance by calling `this.extend(ParentCtor)`
within your decalration function.
@param {Function} ctor to extend.
@return {Object} prototype of parent ctor.
*/
extend: function (ctor) {
return (this[dataKey].extend=ctor).prototype;
},
/** augment
Finish a partial declaration.
TODO: test for bugs, possibly retroactively fix child classes when augmenting parent.
@param {Function} ctor to augment.
@return {Object} prototype of partial ctor.
*/
augment: function (ctor) {
return (this[dataKey].augment=ctor).prototype;
}
};
/** decl
Create a prototype object and return its constructor.
@param {Function|Object} declaration
*/
function decl (declaration) {
if (!declaration) {
declaration = {};
}
else if (declaration.call) {
declaration.prototype=proto;
declaration.prototype[dataKey]={};
}
return getCtor(declaration);
}
/** setDataKey
Sets the name of the property where declaration objects'
metadata will be stored. If you want to pass objects to decl
instead of functions, put the metadata (parent, partial, etc.)
in this property.
@param {String} String value to use for dataKey
*/
decl.setDataKey = function (value) { dataKey=value; };
/** clone
Create a copy of a simple object.
@param {Object} obj
@return {Object} clone of obj.
*/
function clone (object) {
var r=new Clone(Clone.prototype=object);
Clone.prototype={};
return r;
};
/** merge
Merge src object's properties into target object.
@param {Object} target object to merge properties into.
@param {Object} src object to merge properties from.
@return {Object} target for chaining.
*/
function merge (target, src) {
for (var k in src) {
if (src.hasOwnProperty(k) && k!='prototype' && k!=dataKey) {
target[k] = src[k];
}
}
return target;
};
/** getCtor
Prepare a constructor to be returned by decl.
@param {Function|Object} declaration
@return {Function} constructor.
*/
function getCtor (declaration) {
var oldProto,
declFn = declaration.call ? declaration : null,
declObj = declFn ? new declFn(declFn) : declaration,
data = declObj[dataKey] || {},
parent = data.extend, partial = data.augment,
ctor = // user-defined ctor
declObj.hasOwnProperty('constructor') ? declObj.constructor :
// ctor already defined (partial)
partial ? partial :
// generated wrapper for parent ctor
parent ? decl.wrap(parent) :
// generated empty function
new Function();
// If there's a parent constructor, use a clone of its prototype
// and copy the properties from the current prototype.
if (parent) {
oldProto = ctor.prototype;
ctor.prototype = clone(parent.prototype);
merge(ctor.prototype, oldProto);
}
// Merge the declaration function's properties into the constructor.
// This allows adding properties to `this.constructor` in the declaration function
// without defining a constructor, or before defining one.
merge(ctor, declFn);
// Merge the declaration objects's properties into the prototype.
merge(ctor.prototype, declObj);
// Have the constructor reference itself in its prototype, and return it.
return (ctor.prototype.constructor=ctor);
};
return decl;
}());
// This is outside of the main closure so wrapper functions
// will have as short a lookup chain as possible.
/** wrap
Generate wrapper for parent constructor.
@param {Function} parent constructor to wrap.
@return {Function} child constructor.
*/
decl.wrap = function (parent) {
return function(){ parent.apply(this, arguments); };
};
And after:
var decl = (function(_p, _c){
var Clone = new Function(), // dummy function for prototypal cloning
/** dataKey
The name of the property where declaration objects'
metadata will be stored. If you want to pass objects to decl
instead of functions, put the metadata (parent, partial, etc.)
in this property.
*/
dataKey = 'decl-data',
/** proto
This object is used as a prototype for declaration objects,
so all properties are available as properties of `this`
inside the body of each declaration function.
*/
proto = {
/** extend
Perform prototypal inheritance by calling `this.extend(ParentCtor)`
within your decalration function.
@param {Function} ctor to extend.
@return {Object} prototype of parent ctor.
*/
extend: function (ctor) {
return (this[dataKey].extend=ctor)[_p];
},
/** augment
Finish a partial declaration.
TODO: test for bugs, possibly retroactively fix child classes when augmenting parent.
@param {Function} ctor to augment.
@return {Object} prototype of partial ctor.
*/
augment: function (ctor) {
return (this[dataKey].augment=ctor)[_p];
}
};
/** decl
Create a prototype object and return its constructor.
@param {Function|Object} declaration
*/
function decl (declaration) {
if (!declaration) {
declaration = {};
}
else if (declaration.call) {
declaration[_p]=proto;
declaration[_p][dataKey]={};
}
return getCtor(declaration);
}
/** setDataKey
Sets the name of the property where declaration objects'
metadata will be stored. If you want to pass objects to decl
instead of functions, put the metadata (parent, partial, etc.)
in this property.
@param {String} String value to use for dataKey
*/
decl.setDataKey = function (value) { dataKey=value; };
/** clone
Create a copy of a simple object.
@param {Object} obj
@return {Object} clone of obj.
*/
function clone (object) {
var r=new Clone(Clone[_p]=object);
Clone[_p]={};
return r;
};
/** merge
Merge src object's properties into target object.
@param {Object} target object to merge properties into.
@param {Object} src object to merge properties from.
@return {Object} target for chaining.
*/
function merge (target, src) {
for (var k in src) {
if (src.hasOwnProperty(k) && k!=_p && k!=dataKey) {
target[k] = src[k];
}
}
return target;
};
/** getCtor
Prepare a constructor to be returned by decl.
@param {Function|Object} declaration
@return {Function} constructor.
*/
function getCtor (declaration) {
var oldProto,
declFn = declaration.call ? declaration : null,
declObj = declFn ? new declFn(declFn) : declaration,
data = declObj[dataKey] || {},
parent = data.extend, partial = data.augment,
ctor = // user-defined ctor
declObj.hasOwnProperty(_c) ? declObj[_c] :
// ctor already defined (partial)
partial ? partial :
// generated wrapper for parent ctor
parent ? decl.wrap(parent) :
// generated empty function
new Function();
// If there's a parent constructor, use a clone of its prototype
// and copy the properties from the current prototype.
if (parent) {
oldProto = ctor[_p];
ctor[_p] = clone(parent[_p]);
merge(ctor[_p], oldProto);
}
// Merge the declaration function's properties into the constructor.
// This allows adding properties to `this.constructor` in the declaration function
// without defining a constructor, or before defining one.
merge(ctor, declFn);
// Merge the declaration objects's properties into the prototype.
merge(ctor[_p], declObj);
// Have the constructor reference itself in its prototype, and return it.
return (ctor[_p][_c]=ctor);
};
return decl;
}('prototype', 'constructor'));
// This is outside of the main closure so wrapper functions
// will have as short a lookup chain as possible.
/** wrap
Generate wrapper for parent constructor.
@param {Function} parent constructor to wrap.
@return {Function} child constructor.
*/
decl.wrap = function (parent) {
return function(){ parent.apply(this, arguments); };
};