30
abr 13

ECMAScript 5 _.extend

A algunos ya os he comentado los problemas que NC Zakas comenta con el _.extend de underscore y los getters:

var a = {
  init: function() {
    this.list = [];
  },

  get first() {
    return this.list[0];
  }
};

O

var a = {
  init: function() {
    this.list = [];
  }
};

Object.defineProperty(a, 'first', {
  get: function() {
    return this.list[0];
  }
});

Y que si hacemos _.extend(a) va a fallar porque intentará hacer result.first = a.first ejecutando el getter. Pero como aún no hemos llamado al .init() en a porque solo es un prototipo, la propiedad .list es undefined produciendo un error.

Pues bien, yo creía que esto se solucionaba extrayendo el descriptor de la propiedad y usando ese mismo descriptor para copiar la propiedad al nuevo objeto:

function extend(obj) {
  var result = {};
  var properties = Object.keys(obj);

  properties.forEach(function(prop) {
    var descriptor = Object.getOwnPropertyDescriptor(obj, prop);
    Object.defineProperty(result, prop, descriptor);
  });

  return result;
}

Pero he usado esto en la librería de promises y me he dado cuenta que solo hereda las propiedades directas, tanto Object.keys como Object.getOwnPropertyDescriptor solo funcionan si la propiedad está directamente declarada en el objeto y no en ninguno de sus prototipos. Y no hay ninguna forma de decir Object.getPropertyDescriptorFromHimOrHisPrototypes() así que la única forma es recorrer todos los prototipos.

function extend(obj) {
  var result = {};
  var proto = obj;

  while (proto) {
    Object.keys(proto).forEach(function(prop) {
      var descriptor = Object.getOwnPropertyDescriptor(proto, prop);
      Object.defineProperty(result, prop, descriptor);
    });

    proto = Object.getPrototypeOf(proto);
  }

  return result;
}

Pero eso tampoco funciona del todo bien porque las propiedades de los ancestros se impondrían sobre las propiedades propias del objeto. Así que primero hay que empezar por el último prototipo y acabar por el propio objeto

function extend(obj) {
  var result = {};
  var proto = obj;
  var protos = [];

  while (proto) {
    protos.push(proto);
    proto = Object.getPrototypeOf(proto);
  }

  protos.reverse().forEach(function(ancestor) {
    Object.keys(ancestor).forEach(function(prop) {
      var descriptor = Object.getOwnPropertyDescriptor(ancestor, prop);
      Object.defineProperty(result, prop, descriptor);
    });
  });

  return result;
}

Y esto ya funciona, pero le falta un detallito, mediante el property descriptor se puede poner que una propiedad no sea enumerable por lo que no se interará sobre ella con un for .. in .. ni con Object.keys pero si las podemos obtener si utilizamos Object.getOwnPropertyNames (Fuente: MDN). Así que remplazando Object.keys con Object.getOwnPropertyNames esta vez si (espero, rezo, suplico -.-) tenemos una función que crea una copia de todas las propiedades propias y heredadas de un objeto.

function ecma5extend(obj) {
  var proto = obj;
  var protos = [];
  var result = {};

  while (proto) {
    protos.push(proto);
    proto = Object.getPrototypeOf(proto);
  }

  protos.reverse().forEach(function(ancestor) {
    Object.getOwnPropertyNames(ancestor).forEach(function(prop) {
      var descriptor = Object.getOwnPropertyDescriptor(ancestor, prop);
      Object.defineProperty(result, prop, descriptor);
    });
  });

  return result;
}

Como al final el código ha quedado bastante más complejo de lo que me gustaría lo he apuntado en un Gist que podría venirles bien.


18
abr 13

Object.create vs new

JotaEseros! Tengo un dilema existencial que me impide dormir.

Hasta donde sé los constructores hacen más o menos esto

function fakeNew(Ctor) {
  var instance = Object.create(Ctor.prototype);
  instance.constructor();
  return instance;
}

Todo empezó cuando quise hacer polyfill de Object.create(), así podría crear objectos sin usar new, decidí usar la versión sencilla

Object.create = function(proto) {
  function F() { }
  F.prototype = proto;
  return new F();
}

Y empecé a crear un montón de objetos y a prototiparlos, pero como los objetos muchas veces necesitaban inicializar sus propiedades les hice el método .init()

var base = {
  init: function() {
    EmitterMixin.call(this);
    return this;
  }
};

var obj = Object.create(base).init();

Pero claro, tengo que acordarme de devolver this siempre al acabar .init() y muchas veces lo olvidaba o me olvidaba de invocar .init() que es peor. Así que intentando como simplificar la inicialización de un objeto pensé crear una funcioń global que se encargara de invocar a Object.create, llamar a la función inicializadora y devolver this:

function create(proto) {
  var child = Object.create(proto);
  child.init();
  return child;
}

Y me empezaron a dar ganas de reventarme la cabeza contra la pared al darme cuenta que lo que estaba haciendo es prácticamente lo mismo que hace el operador nativo new pero mucho más lento y, en caso de usar el polyfill the Object.create incluso usando new por debajo.

Entonces me pregunto, que beneficios aporta abandonar new? es notablemente más rápido en la mayoría de navegadores y por la naturaleza de javascript solemos necesitar crear una función inicializadora que en el caso de new es el constructor.

Y lo que es más grave aún, he notado que usaba dos tipos diferentes de objetos, unos “abstractos” y otros “instancias” la mayor diferencia es que en las instancias tenía que invocar .init() siempre mientras que los abstractos no era necesario porque solo serían usados para crear otros objetos que los prototiparan. Y es un patrón que he visto mientras usaba new:

function Foo() { }
Foo.prototype.method = function() { ... };

function Bar() { }
// Objeto "abstracto", no se invoca inicializador
Bar.prototype = Object.create(Foo.prototype);
Bar.prototype.other = function() { ... };

// Objeto "instancia"
// se invoca el constructor como inicializador
var obj = new Bar();

Realmente hay es una ventaja abandonar new? estamos seguros que no se trata de una herramienta de alto nivel que simplifica algo que igualmente tendremos que hacer nosotros a mano? Que pros y contras tienen new y Object.create?


07
dic 11

Let’s get it started!

Bueno, ha llegado la hora de dar el siguiente paso como programador: comenzar un blog para que todos puedan criticarme por poner las llaves { a la derecha :D

Y ESTE DE DONDE SALE?

Primero lo primero. Mi nombre es Adrián Matías Quezada pero lo de Adrián está de adorno, tengo 22 años hasta la fecha de los cuales los últimos 5 los he pasado trabajando de programador, a nivel aficionado he estado metido en informática desde que tengo memoria para desgracia de mis padres y para beneficio del la tienda de informática local a la cual acudían cada mes para que arreglara mis desastres con la computadora. Por aquellos tiempos Flash era la última tecnología y gracias al código ActionScript embebido en los frames descubrí que podía controlar el flujo de una película .swf. Desde entonces he me ha ido interesando cada vez más la programación hasta que finalmente aprendí superficialmente C++. Pasaron los años y empecé a trabajar por lo que pude empezar en aplicaciones serias con C#.NET, Java, Python, etc… Finalmente me hice freelance y conseguí un puesto en una RIA bastante compleja gracias a la cual descubrí Javascript y sus good parts con el cual llevo dos años trabajando y disfrutando de sus secretos.

DE QUE TRATA ÉSTE BLOG?

El tema de éste blog me costó bastante definirlo, básicamente es lugar donde pueda compartir mis experiencias profesionales y aficionadas sobre programación, administración de sistemas e informática en general. Principalmente hablaré de Javascript ya que trabajando con él cada día me surgen incesantemente preguntas y soluciones y sobre programación… llamémosle semántica, conceptos aplicables a cualquier lenguaje. Para empezar hay varios temas que me gustaría tratar que los listaré aquí para no olvidarlos y porque soy demasiado vago para hacer una lista de ToDo:

  • Buenas Prácticas
  • El patrón Promise
  • Programming Style & Mind (Douglas Crockford)
  • Javascript: Conceptos básicos
  • GNU/Linux: Comandos útiles

Espero poder tenerlos todos antes de que acabe el mes, mi habilidad para gestionar el tiempo es próxima a cero.   Por el momento eso es todo, espero contribuír a muchos autodidactas como me ayudaron a mi las páginas de mi época.


07
dic 11

Hello World!

int main(char** args) {
  print("Hello World!");
  return 0;
}

Y así es como un blog de programación debe empezar.