Taglio della dimensione dello script utilizzando la notazione della matrice per le proprietà ad accesso frequente

2

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); };
};
    
posta Hey 13.01.2012 - 09:49
fonte

2 risposte

2

Sospetto che le scorciatoie siano più lente, il che probabilmente è il motivo per cui il minificatore non applica tale trasformazione. Almeno google chrome ha ottimizzato il compilatore di codice di byte per javascript e potrebbe essere in grado di prendere un collegamento o due quando sa che il nome del membro è costante, cosa che non avviene quando viene utilizzata la variabile.

Non raccomanderei assolutamente di farlo manualmente, ma se lo volessi davvero, dovresti farlo come parte del minification. La leggibilità dannosa non vale la pena.

    
risposta data 13.01.2012 - 10:31
fonte
2

Tieni presente che qualsiasi server configurato in modo corretto invierà lo script utilizzando la compressione, che annullerà il risparmio di dimensioni per le stringhe di uso frequente, ma lascerà comunque il minor costo di esecuzione e il significativo costo di leggibilità e manutenzione. Quindi direi che non ne vale la pena.

    
risposta data 13.01.2012 - 17:26
fonte

Leggi altre domande sui tag