Ho notato una certa ridondanza in uno script che ho eseguito tramite 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)}};
Sembra un grande mare di prototype
e constructor
, non è vero?
Sono stato in grado di portarlo a circa 6/7 della dimensione dell'originale memorizzando le stringhe "costruttore" e "prototipo" e utilizzando la notazione dell'array ovunque per accedere a prototipi e costruttori. Il risparmio di dimensioni dovrebbe aumentare con l'aumentare delle dimensioni della sceneggiatura. Ecco come appare dopo la modifica:
(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)}};
Vale la pena di migliorare la leggibilità dei danni per salvare pochi byte? È difficile sapere quando fermarsi ... hasOwnProperty
appare solo due volte, potrei usarlo ancora una volta. Quindi, dovrei sostituire .hasOwnProperty
con [_has]
? Dovrei semplicemente fermarmi qui? O dovrei rimetterlo così com'è, e non preoccuparti dei pochi extra byte?
D'altra parte, forse questo non danneggia la leggibilità del tutto, e potrebbe essere semplicemente visto come scorciatoie convenienti.
Un'altra alternativa potrebbe essere l'astrazione del comportamento utilizzando macro ; scrivi .prototype
e convertilo in [_p]
al momento della compilazione, ovviamente dichiarando _p
da qualche parte. Ma ciò sembra eccessivamente complicato per un'ottimizzazione delle dimensioni relativamente minore. A proposito, mi chiedo perché il compilatore di chiusura non faccia già qualcosa del genere?
Sono curioso di sapere cosa ne pensa la comunità.
Per riferimento, la fonte non minificata prima dell'ottimizzazione della dimensione:
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); };
};
E dopo:
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); };
};