javascript new - Usando “Object.create” en lugar de “nuevo”




constructor this (12)

Javascript 1.9.3 / ECMAScript 5 presenta Object.create , que Douglas Crockford, entre otros, ha advocating durante mucho tiempo. ¿Cómo reemplazo el código new a continuación con Object.create ?

var UserA = function(nameParam) {
    this.id = MY_GLOBAL.nextId();
    this.name = nameParam;
}
UserA.prototype.sayHello = function() {
    console.log('Hello '+ this.name);
}
var bob = new UserA('bob');
bob.sayHello();

(Supongamos que MY_GLOBAL.nextId existe).

Lo mejor que se me ocurre es:

var userB = {
    init: function(nameParam) {
        this.id = MY_GLOBAL.nextId();
        this.name = nameParam;
    },
    sayHello: function() {
        console.log('Hello '+ this.name);
    }
};
var bob = Object.create(userB);
bob.init('Bob');
bob.sayHello();

No parece haber ninguna ventaja, así que creo que no lo estoy consiguiendo. Probablemente estoy siendo demasiado neoclásico. ¿Cómo debo usar Object.create para crear el usuario 'bob'?


Answers

TL; DR:

new Computer() invocará la función del constructor Computer(){} por una vez, mientras que Object.create(Computer.prototype) no lo hará.

Todas las ventajas se basan en este punto.

Aunque por algunas razones de optimización del motor Object.create , Object.create puede ser más lento, lo que no es intuitivo.


Con solo un nivel de herencia, es posible que su ejemplo no le permita ver los beneficios reales de Object.create .

Este método le permite implementar fácilmente la herencia diferencial , donde los objetos pueden heredar directamente de otros objetos.

En su ejemplo de userB , no creo que su método de init deba ser público o incluso existir, si llama nuevamente a este método en una instancia de objeto existente, las propiedades de id y name cambiarán.

Object.create permite inicializar las propiedades del objeto utilizando su segundo argumento, por ejemplo:

var userB = {
  sayHello: function() {
    console.log('Hello '+ this.name);
  }
};

var bob = Object.create(userB, {
  'id' : {
    value: MY_GLOBAL.nextId(),
    enumerable:true // writable:false, configurable(deletable):false by default
  },
  'name': {
    value: 'Bob',
    enumerable: true
  }
});

Como puede ver, las propiedades se pueden inicializar en el segundo argumento de Object.create , con un objeto literal utilizando una sintaxis similar a la utilizada por los métodos Object.defineProperties y Object.defineProperty .

Te permite establecer los atributos de propiedad ( enumerable , writable o configurable ), que pueden ser realmente útiles.


Realmente no hay ninguna ventaja en el uso de Object.create(...) sobre un new object .

Quienes abogan por este método generalmente declaran ventajas bastante ambiguas: "scalability" o " más natural para JavaScript ", etc.

Sin embargo, todavía tengo que ver un ejemplo concreto que muestra que Object.create tiene alguna ventaja sobre el uso de new . Por el contrario hay problemas conocidos con él. Sam Elsamman describe lo que sucede cuando hay objetos anidados y se Object.create(...) :

var Animal = {
    traits: {},
}
var lion = Object.create(Animal);
lion.traits.legs = 4;
var bird = Object.create(Animal);
bird.traits.legs = 2;
alert(lion.traits.legs) // shows 2!!!

Esto ocurre porque Object.create(...) aboga por una práctica en la que los datos se utilizan para crear nuevos objetos; Aquí el dato Animal convierte en parte del prototipo de lion y bird , y causa problemas cuando se comparte. Cuando se usa nuevo, la herencia prototípica es explícita:

function Animal() {
    this.traits = {};
}

function Lion() { }
Lion.prototype = new Animal();
function Bird() { }
Bird.prototype = new Animal();

var lion = new Lion();
lion.traits.legs = 4;
var bird = new Bird();
bird.traits.legs = 2;
alert(lion.traits.legs) // now shows 4

Con respecto a los atributos de propiedad opcionales que se pasan a Object.create(...) , estos pueden agregarse usando Object.defineProperties(...) .


La ventaja es que Object.create suele ser más lento que new en la mayoría de los navegadores

En este ejemplo de jsperf , en un Chromium, el new navegador es 30 veces más rápido que Object.create(obj) aunque ambos son bastante rápidos. Todo esto es bastante extraño porque lo nuevo hace más cosas (como invocar a un constructor) donde Object.create debería estar creando un nuevo Objeto con el objeto pasado como un prototipo (enlace secreto en Crockford-habla)

Tal vez los navegadores no hayan alcanzado el Object.create hacer que Object.create más eficiente (quizás lo están basando en lo new bajo las coberturas ... incluso en el código nativo)


Resumen:

  • Object.create() es una función de Javascript que toma 2 argumentos y devuelve un nuevo objeto.
  • El primer argumento es un objeto que será el prototipo del objeto recién creado.
  • El segundo argumento es un objeto que será las propiedades del objeto recién creado.

Ejemplo:

const proto = {
  talk : () => console.log('hi')
}

const props = {
  age: {
    writable: true,
    configurable: true,
    value: 26
  }
}


let Person = Object.create(proto, props)

console.log(Person.age);
Person.talk();

Aplicaciones prácticas:

  1. La principal ventaja de crear un objeto de esta manera es que el prototipo se puede definir explícitamente . Al usar un objeto literal, o la new palabra clave, no tiene control sobre esto (sin embargo, puede sobrescribirlos, por supuesto).
  2. Si queremos tener un prototipo La new palabra clave invoca una función constructora. Con Object.create() no hay necesidad de invocar o incluso declarar una función constructora .
  3. Básicamente, puede ser una herramienta útil cuando desea crear objetos de una manera muy dinámica. Podemos hacer una función de fábrica de objetos que crea objetos con diferentes prototipos dependiendo de los argumentos recibidos.

Otro uso posible de Object.create es clonar objetos inmutables de una manera económica y efectiva .

var anObj = {
    a: "test",
    b: "jest"
};

var bObj = Object.create(anObj);

bObj.b = "gone"; // replace an existing (by masking prototype)
bObj.c = "brand"; // add a new to demonstrate it is actually a new obj

// now bObj is {a: test, b: gone, c: brand}

Notas : El fragmento de código anterior crea un clon de un objeto de origen (es decir, no es una referencia, como en cObj = aObj). Se beneficia sobre el método de propiedades de copia (ver 1 ), ya que no copia las propiedades de los miembros objeto. Más bien, crea otro objeto de destino con su conjunto de prototipos en el objeto de origen. Además, cuando las propiedades se modifican en el objeto dest, se crean "sobre la marcha", enmascarando las propiedades del prototipo (src). Esto constituye una forma rápida y efectiva de clonar objetos inmutables.

La advertencia aquí es que esto se aplica a los objetos de origen que no deben modificarse después de la creación (inmutable). Si el objeto de origen se modifica después de la creación, también se modificarán todas las propiedades desenmascaradas del clon.

Toque aquí ( http://jsfiddle.net/y5b5q/1/ ) (necesita Object.create un navegador capaz).


Object.create aún no es estándar en varios navegadores, por ejemplo, IE8, Opera v11.5, Konq 4.3 no lo tienen. Puede usar la versión de Douglas Crockford de Object.create para esos navegadores, pero esto no incluye el segundo parámetro de "objeto de inicialización" usado en la respuesta de CMS.

Para el código de navegador cruzado, una forma de obtener la inicialización del objeto mientras tanto es personalizar el Object.create de Crockford. Aquí hay un método:

Object.build = function(o) {
   var initArgs = Array.prototype.slice.call(arguments,1)
   function F() {
      if((typeof o.init === 'function') && initArgs.length) {
         o.init.apply(this,initArgs)
      }
   }
   F.prototype = o
   return new F()
}

Esto mantiene la herencia prototípica de Crockford, y también verifica cualquier método de inicio en el objeto, luego lo ejecuta con su (s) parámetro (s), como decir new man ('John', 'Smith'). Su código se convierte en: -

MY_GLOBAL = {i: 1, nextId: function(){return this.i++}}  // For example

var userB = {
    init: function(nameParam) {
        this.id = MY_GLOBAL.nextId();
        this.name = nameParam;
    },
    sayHello: function() {
        console.log('Hello '+ this.name);
    }
};
var bob = Object.build(userB, 'Bob');  // Different from your code
bob.sayHello();

Así que bob hereda el método sayHello y ahora tiene propiedades propias id = 1 y name = 'Bob'. Estas propiedades son de escritura y enumerables, por supuesto. Esta es también una forma mucho más sencilla de inicializar que para ECMA Object.create, especialmente si no está preocupado por los atributos de escritura, enumerables y configurables.

Para la inicialización sin un método init, se podría usar el siguiente mod de Crockford:

Object.gen = function(o) {
   var makeArgs = arguments 
   function F() {
      var prop, i=1, arg, val
      for(prop in o) {
         if(!o.hasOwnProperty(prop)) continue
         val = o[prop]
         arg = makeArgs[i++]
         if(typeof arg === 'undefined') break
         this[prop] = arg
      }
   }
   F.prototype = o
   return new F()
}

Esto llena las propiedades propias del usuario B, en el orden en que se definen, utilizando los parámetros Object.gen de izquierda a derecha después del parámetro usuarioB. Utiliza el bucle for (prop in o), por lo que, según los estándares de ECMA, el orden de enumeración de la propiedad no se puede garantizar de la misma manera que el orden de definición de la propiedad. Sin embargo, varios ejemplos de código probados en (4) los principales navegadores muestran que son iguales, siempre que se use el filtro hasOwnProperty, y algunas veces incluso si no.

MY_GLOBAL = {i: 1, nextId: function(){return this.i++}};  // For example

var userB = {
   name: null,
   id: null,
   sayHello: function() {
      console.log('Hello '+ this.name);
   }
}

var bob = Object.gen(userB, 'Bob', MY_GLOBAL.nextId());

Algo más simple, diría que Object.build, ya que userB no necesita un método init. Además, userB no es específicamente un constructor, pero parece un objeto singleton normal. Así que con este método puede construir e inicializar desde objetos normales normales.


Podrías hacer que el método init devuelva this , y luego encadenar las llamadas, así:

var userB = {
    init: function(nameParam) {
        this.id = MY_GLOBAL.nextId();
        this.name = nameParam;
        return this;
    },
    sayHello: function() {
        console.log('Hello '+ this.name);
    }
};

var bob = Object.create(userB).init('Bob');

A veces no puede crear un objeto con NEW pero aún puede invocar el método CREATE.

Por ejemplo: si desea definir un elemento personalizado, debe derivar de HTMLElement.

proto = new HTMLElement  //fail :(
proto = Object.create( HTMLElement.prototype )  //OK :)
document.registerElement( "custom-element", { prototype: proto } )

Tienes que hacer una función personalizada Object.create() . Una que aborde las preocupaciones de Crockford y también llame a su función de inicio.

Esto funcionará:

var userBPrototype = {
    init: function(nameParam) {
        this.name = nameParam;
    },
    sayHello: function() {
        console.log('Hello '+ this.name);
    }
};


function UserB(name) {
    function F() {};
    F.prototype = userBPrototype;
    var f = new F;
    f.init(name);
    return f;
}

var bob = UserB('bob');
bob.sayHello();

Aquí UserB es como Object.create, pero ajustado a nuestras necesidades.

Si quieres, también puedes llamar al:

var bob = new UserB('bob');

Si bien Douglas Crockford solía ser un entusiasta defensor de Object.create () y básicamente es la razón por la que este constructo está en JavaScript, ya no tiene esta opinión.

Dejó de usar Object.create, porque dejó de usar esta palabra clave por completo, ya que causa demasiados problemas. Por ejemplo, si no tiene cuidado, puede apuntar fácilmente al objeto global, lo que puede tener muy malas consecuencias. Y afirma que sin usar este Object.create ya no tiene sentido.

Puedes ver este video de 2014 donde habla en Nordic.js:

https://www.youtube.com/watch?v=PSGEjv3Tqo0


No soy un experto en script java pero aquí hay un ejemplo simple para entender la diferencia entre "Object.create" y "new".

paso 1: crea la función principal con algunas propiedades y acciones.

function Person() {

this.name = 'venkat';

this.address = 'dallas';

this.mobile='xxxxxxxxxx'

}

Person.prototype.func1 = function () {

    return this.name + this.address;
}

paso 2: crea una función hija (PersonSalary) que se extiende por encima de la función Person usando New keyword ..

function PersonSalary() {
    Person.call(this);
}
PersonSalary.prototype = new Person();

PersonSalary();

paso 3: crear la segunda función secundaria (PersonLeaves) que se extiende por encima de la función Person utilizando la palabra clave Object.create .

function PersonLeaves() {
 Person.call(this);
}
PersonLeaves.prototype = Object.create(Person.prototype);


PersonLeaves();

// Ahora comprueba ambos prototipos de funciones infantiles.

PersonSalary.prototype
PersonLeaves.prototype

Ambas funciones secundarias se vincularán con el prototipo de Persona (función principal) y pueden acceder a sus métodos, pero si crea una función hija usando nueva, devolverá un objeto completamente nuevo con todas las propiedades principales que no necesitamos y también cuando cree cualquier Objeto o función usando "Nuevo" esa función primaria se ejecuta y no queremos que sea.

Aquí están los detalles

si solo desea delegar en algunos métodos en la función primaria y no desea que se cree un nuevo objeto, usar Object.create es la mejor manera.





javascript constructor new-operator object-create