7

AntecedentesAMPLÍA reto: macros de preprocesador de función y programación orientada a objetos de clase similar

He estado usando el preprocesador C para gestionar y "compilar" semi-grandes proyectos de javascript con varios archivos y construir objetivos. Esto le da acceso total a las directivas de preprocesador C como #include, #define, #ifdef, etc. desde javascript. He aquí una muestra de escritura de la estructura para que pueda probar el código de ejemplo:

#!/bin/bash 
export OPTS="-DDEBUG_MODE=1 -Isrc" 
for FILE in `find src/ | egrep '\.js?$'` 
do 
    echo "Processing $FILE" 
    cat $FILE \ 
    | sed 's/^\s*\/\/#/#/' \ 
    | cpp $OPTS \ 
    | sed 's/^[#:<].*// ; /^$/d' \ 
    > build/`basename $FILE`; 
done 

Hacer un directorio y una srcbuild, y poner los archivos .js en src.


Conveniencia macros

Originalmente, sólo quería las cosas preprocesador para #include y tal vez un par de #ifdef s, pero me puse a pensar, ¿no sería bueno tener algunos macros convenientes demasiado ? La experimentación se produjo.

#define EACH(o,k)  for (var k in o) if (o.hasOwnProperty(k)) 

frío, así que ahora puedo escribir algo como esto:

EACH (location, prop) { 
    console.log(prop + " : " location[prop]); 
} 

Y se ampliará a:

for (var prop in location) if (location.hasOwnProperty(prop)) { 
    console.log(prop + " : " location[prop]); 
} 

¿Qué tal foreach?

#define FOREACH(o,k,v) var k,v; for(k in o) if (v=o[k], o.hasOwnProperty(k)) 
// ... 
FOREACH (location, prop, val) { console.log(prop + " : " + val) } 

Aviso cómo nos colamos v=o[k] dentro de la condición if por lo que no perturbe las llaves que deben seguir a la invocación de esta macro.


Clase-como programación orientada a objetos

Vamos a empezar con una macro espacio de nombres y un patrón de JS oscura pero útil ...

#define NAMESPACE(ns) var ns = this.ns = new function() 

new function(){ ... } hace algunas cosas muy buenas. Llama a una función anónima como un constructor, por lo que no necesita un () adicional al final para llamarlo, y dentro de él this se refiere al objeto que está creando el constructor, en otras palabras, el propio espacio de nombres. Esto también nos permite anidar espacios de nombres dentro de espacios de nombres.

Aquí es mi juego completo de macros de programación orientada a objetos de clase similar:

#define NAMESPACE(ns) var ns=this.ns=new function() 

#define CLASS(c)  var c=this;new function() 

#define CTOR(c)  (c=c.c=this.constructor=$$ctor).prototype=this;\ 
         function $$ctor 

#define PUBLIC(fn) this.fn=fn;function fn 
#define PRIVATE(fn) function fn 
#define STATIC(fn) $$ctor.fn=fn;function fn 

Como se puede ver, estas macros definen muchas cosas tanto en el Variable Object (por conveniencia) y en this (por necesidad). Aquí hay un código de ejemplo:

NAMESPACE (Store) { 

    CLASS (Cashier) { 

    var nextId = 1000; 

    this.fullName = "floater"; 

    CTOR (Cashier) (fullName) { 
     if (fullName) this.fullName = fullName; 
     this.id = ++nextId; 
     this.transactions = 0; 
    } 

    PUBLIC (sell) (item, customer) { 
     this.transactions += 1; 
     customer.inventory.push(item); 
    } 

    STATIC (hire) (count) { 
     var newCashiers = []; 
     for (var i=count; i--;) { 
     newCashiers.push(new Cashier()); 
     } 
     return newCashiers; 
    } 
    } 

    CLASS (Customer) { 

    CTOR (Customer) (name) { 
     this.name = name; 
     this.inventory = []; 
     this.transactions = 0; 
    } 

    PUBLIC (buy) (item, cashier) { 
     cashier.sell(this, item); 
    } 
    } 
} 

¿Y AMPLÍA?

Así que esto me lleva a la pregunta ... ¿cómo podemos implementar EXTENDS como una macro para envolver el habitual "clonar el prototipo, copiar las propiedades del constructor" js prototype inheritance? No he encontrado una manera de hacerlo fuera de requerir que el EXTENDS aparezca después de la definición de clase, que es tonto. Este experimento necesita EXTENSIÓN o es inútil. Siéntase libre de cambiar las otras macros siempre que den los mismos resultados.

Editar - Esto puede ser útil para EXTENDS; listando aquí para completar.

#define EACH(o,k) for(var k in o)if(o.hasOwnProperty(k)) 
#define MERGE(d,s) EACH(s,$$i)d[$$i]=s[$$i] 
#define CLONE(o) (function(){$$C.prototype=o;return new $$C;function $$C(){}}()) 

Gracias de antemano por cualquier ayuda, consejo o discusión animada. :)

Respuesta

2

Creo que acabo de completar mi propio desafío. Agregué un segundo argumento (opcional) a la macro de declaración CLASS para la superclase de la clase que se está declarando.

Mi implementación original creó una gran cantidad de basura en línea alrededor del constructor, por lo que decidí incluir algunas funciones de conveniencia en un objeto de macro ayudante para evitar la redundancia.

Estas son las encarnaciones actuales de mis macros de programación orientada a objetos de clase similar:

// class-like oo 

#ifndef BASE 
    #define BASE $$_ 
#endif 

#define COLLAPSE(code)  code 

#define NAMESPACE(ns)  var ns=BASE._ns(this).ns=new function() 

#define CLASS(c,__ARGS...) var c=[BASE._class(this),[__ARGS][0]]; \ 
          new function() 

#define CTOR(c)    BASE._extend($$_##c,c[1],this); \ 
          c=c[0].c=$$_##c; function $$_##c 

#define PUBLIC(fn)   BASE._public(this).fn=fn;function fn 

#define PRIVATE(fn)   function fn 

#define STATIC(fn)   BASE._static(this).fn=fn;function fn 

// macro helper object 

COLLAPSE(var BASE=new function(){ 

    function Clone(){}; 

    function clone (obj) { 
    Clone.prototype=obj; return new Clone; 
    }; 

    function merge (sub, sup) { 
    for (var p in sup) if (sup.hasOwnProperty(p)) sub[p]=sup[p]; 
    }; 

    this._extend = function (sub, sup, decl) { 
    if (sup) { 
     merge(sub, sup); 
     sub.prototype=clone(sup.prototype); 
     sub.prototype.constructor=sub; 
    }; 
    if (decl) { 
     merge(sub.prototype, decl); 
     decl._static=sub; 
     decl._public=sub.prototype; 
    }; 
    }; 

    this._static=this._ns=this._class=function (obj) { 
    return (obj._static || obj); 
    }; 

    this._public=function (obj) { 
    return (obj._public || obj); 
    }; 

}) 

... Aquí hay un espacio de nombres de pruebas ...

//#include "macros.js" 

NAMESPACE (Store) { 

    CLASS (Cashier) { 

    var nextId = 1000; 

    this.fullName = "floater"; 

    CTOR (Cashier) (fullName) { 
     if (fullName) this.fullName = fullName; 
     this.id = ++nextId; 
     this.transactions = 0; 
    } 

    PUBLIC (sell) (item, customer) { 
     this.transactions += 1; 
     customer.inventory.push(item); 
    } 

    STATIC (hire) (count) { 
     var newCashiers = []; 
     for (var i=count; i--;) { 
     newCashiers.push(new Cashier()); 
     } 
     return newCashiers; 
    } 
    } 

    // Customer extends Cashier, just so we can test inheritance 

    CLASS (Customer, Cashier) { 

    CTOR (Customer) (name) { 
     this.name = name; 
     this.inventory = []; 
     this.transactions = 0; 
    } 

    PUBLIC (buy) (item, cashier) { 
     cashier.sell(this, item); 
    } 

    CLASS (Cart) { 

     CTOR (Cart) (customer) { 
     this.customer = customer; 
     this.items = []; 
     } 
    } 

    } 
} 

... y aquí está la salida ..

var $$_=new function(){ function Clone(){}; function clone (obj) { Clone.prototype=obj; return new Clone; }; function merge (sub, sup) { for (var p in sup) if (sup.hasOwnProperty(p)) sub[p]=sup[p]; }; this._extend = function (sub, sup, decl) { if (sup) { merge(sub, sup); sub.prototype=clone(sup.prototype); sub.prototype.constructor=sub; }; if (decl) { merge(sub.prototype, decl); decl._static=sub; decl._public=sub.prototype; }; }; this._static=this._ns=this._class=function (obj) { return (obj._static || obj); }; this._public=function (obj) { return (obj._public || obj); }; } 
var Store=$$_._ns(this).Store=new function() { 
    var Cashier=[$$_._class(this),[][0]]; new function() { 
    var nextId = 1000; 
    this.fullName = "floater"; 
    $$_._extend($$_Cashier,Cashier[1],this); Cashier=Cashier[0].Cashier=$$_Cashier; function $$_Cashier (fullName) { 
     if (fullName) this.fullName = fullName; 
     this.id = ++nextId; 
     this.transactions = 0; 
    } 
    $$_._public(this).sell=sell;function sell (item, customer) { 
     this.transactions += 1; 
     customer.inventory.push(item); 
    } 
    $$_._static(this).hire=hire;function hire (count) { 
     var newCashiers = []; 
     for (var i=count; i--;) { 
     newCashiers.push(new Cashier()); 
     } 
     return newCashiers; 
    } 
    } 
    var Customer=[$$_._class(this),[Cashier][0]]; new function() { 
    $$_._extend($$_Customer,Customer[1],this); Customer=Customer[0].Customer=$$_Customer; function $$_Customer (name) { 
     this.name = name; 
     this.inventory = []; 
     this.transactions = 0; 
    } 
    $$_._public(this).buy=buy;function buy (item, cashier) { 
     cashier.sell(this, item); 
    } 
    var Cart=[$$_._class(this),[][0]]; new function() { 
     $$_._extend($$_Cart,Cart[1],this); Cart=Cart[0].Cart=$$_Cart; function $$_Cart (customer) { 
     this.customer = customer; 
     this.items = []; 
     } 
    } 
    } 
} 

La herencia, las clases internas y los espacios de nombres anidados parecen funcionar bien. ¿Qué piensas, es este un enfoque útil para OOP tipo clase y la reutilización de código en js? Avísame si me he perdido algo.

+0

¿Cuáles son los beneficios comparados con GWT? – user123444555621

+0

Bueno, esto sigue siendo javascript, simplemente se extendió con macros. El código GWT está escrito en java y 'compila' en javascript. Entonces, si está familiarizado con javascript y desea una forma conveniente de usar diseños familiares similares a clases, como espacios de nombres, declaraciones de clase con constructores dentro de ellos, herencia, etc., todos estos son posibles de emular en JavaScript, estas macros simplemente hacen es más conveniente. –

Cuestiones relacionadas