Comprender la diferencia entre Object.create () y la nueva SomeFunction ()

392

Recientemente me topé con el Object.create()método en JavaScript, y estoy tratando de deducir cómo es diferente de crear una nueva instancia de un objeto new SomeFunction()y cuándo querría usar uno sobre el otro.

Considere el siguiente ejemplo:

var test = {
  val: 1,
  func: function() {
    return this.val;
  }
};
var testA = Object.create(test);

testA.val = 2;
console.log(test.func()); // 1
console.log(testA.func()); // 2

console.log('other test');
var otherTest = function() {
  this.val = 1;
  this.func = function() {
    return this.val;
  };
};

var otherTestA = new otherTest();
var otherTestB = new otherTest();
otherTestB.val = 2;
console.log(otherTestA.val); // 1 
console.log(otherTestB.val); // 2

console.log(otherTestA.func()); // 1
console.log(otherTestB.func()); // 2

Observe que se observa el mismo comportamiento en ambos casos. Me parece que las principales diferencias entre estos dos escenarios son:

  • El objeto utilizado en Object.create()realidad forma el prototipo del nuevo objeto, mientras que en las new Function()propiedades / funciones declaradas no forman el prototipo.
  • No puede crear cierres con la Object.create()sintaxis como lo haría con la sintaxis funcional. Esto es lógico dado el alcance de tipo léxico (vs bloque) de JavaScript.

¿Son correctas las declaraciones anteriores? ¿Y me estoy perdiendo algo? ¿Cuándo usarías uno sobre el otro?

EDITAR: enlace a la versión jsfiddle del ejemplo de código anterior: http://jsfiddle.net/rZfYL/

Mate
fuente
2
Consulte también Uso de "Object.create" en lugar de "nuevo"
Bergi

Respuestas:

246

El objeto utilizado en Object.create en realidad forma el prototipo del nuevo objeto, mientras que, como en la nueva Función (), las propiedades / funciones declaradas no forman el prototipo.

Sí, Object.createcrea un objeto que hereda directamente del que pasó como primer argumento.

Con las funciones de constructor, el objeto recién creado hereda del prototipo del constructor, por ejemplo:

var o = new SomeConstructor();

En el ejemplo anterior, ohereda directamente de SomeConstructor.prototype.

Hay una diferencia aquí, con Object.createusted puede crear un objeto que no herede de nada Object.create(null);, por otro lado, si establece SomeConstructor.prototype = null;el objeto recién creado heredará de Object.prototype.

No puede crear cierres con la sintaxis Object.create como lo haría con la sintaxis funcional. Esto es lógico dado el alcance de tipo léxico (vs bloque) de JavaScript.

Bueno, puede crear cierres, por ejemplo, utilizando el argumento de descriptores de propiedad:

var o = Object.create({inherited: 1}, {
  foo: {
    get: (function () { // a closure
      var closured = 'foo';
      return function () {
        return closured+'bar';
      };
    })()
  }
});

o.foo; // "foobar"

Tenga en cuenta que estoy hablando del Object.createmétodo ECMAScript 5th Edition , no de la cuña de Crockford.

El método está comenzando a implementarse de forma nativa en los últimos navegadores, consulte esta tabla de compatibilidad .

CMS
fuente
2
@CMS 2 preguntas. 1) ¿La cadena de alcance en Object.create (nulo) todavía termina en el alcance global (como 'ventana' en un navegador), o termina en sí misma? 2) Todavía no me queda claro por qué se introdujo Object.create (por ejemplo, ¿qué característica faltaba que se abordara?) Y por qué uno lo usaría en lugar de la nueva Función ();
Matt
99
@Matt, 1) la cadena de alcance no es realmente un concepto relacionado aquí, la cadena de alcance está relacionada con la resolución del identificador , por ejemplo: cómo foo;se resuelve en el entorno léxico actual . 2) Para proporcionar una manera fácil de implementar la herencia, es una construcción realmente poderosa. En mi opinión, lo usaría porque es realmente simple y liviano, pero para el código de producción, todavía tenemos que esperar un tiempo hasta que ES5 sea ampliamente compatible. Sobre las características faltantes, faltaba el hecho de crear un objeto "prístino", Object.create(null);es realmente útil implementar objetos confiables similares a tablas hash ...
CMS
@CMS Gracias. Entonces, simplemente cuando creas un objeto usando 'Object.create', obtienes la capacidad de seleccionar el objeto que debería ser su prototipo.
Anshul
@CMS OK, ¿entonces Object.create(null)significa que no tienes que usar hasOwnProperty()basura cuando iteras porque no hereda ninguno? Me gusta eso, gracias. Por supuesto, todo el mundo lo seguirá haciendo, hasOwnPropertyya que no todos lo usarán, Object.create(null)así que no estoy seguro de que sea un beneficio real ... Hasta ahora, he encontrado los otros "beneficios" de Object.create()no ser convincentes.
user949300
425

Muy simplemente dicho, new Xes Object.create(X.prototype)con ejecutar adicionalmente la constructorfunción. (Y dando la constructoroportunidad al returnobjeto real que debería ser el resultado de la expresión en lugar de this).

Eso es. :)

El resto de las respuestas son simplemente confusas, porque aparentemente nadie más lee la definición de newninguno de los dos. ;)

Evi1M4chine
fuente
23
+1 ¡Simplicidad y claridad! (Aunque Object.create (null) parece una buena opción, tal vez debería mencionarlo).
user949300
que sea sencillo, ese es el camino a seguir
Bill
Ahora le toca a la cuestión de la "espera, por lo que las funciones tienen prototipos demasiado ? ¿Cuál es la relación entre estos y objetos prototipos?"
Qwertie
3
@Qwertie: en JS, todo es un objeto. :) Ellos copiaron eso de Java, quien lo copió de SmallTalk, quien fue hasta el final con él. Es un buen caso de "emergencia", que hace la vida más fácil en general.
Evi1M4chine
@ Evi1M4chine en realidad en Java, las funciones no son objetos (y tampoco son primitivas, de hecho) ... y los objetos no tienen prototipos, por lo que la comparación parece inadecuada. El hecho de que JS funcione de manera diferente a otros lenguajes OO populares es una fuente importante de confusión (y no ayuda que los navegadores no ofrezcan una manera fácil de visualizar la red de objetos, incluidas funciones y prototipos). PD: encontré útil este enlace: davidwalsh.name/javascript-objects-deconstruction
Qwertie
204

Estos son los pasos que ocurren internamente para ambas llamadas:
(Sugerencia: la única diferencia está en el paso 3)


new Test():

  1. crear new Object()obj
  2. establecido obj.__proto__enTest.prototype
  3. return Test.call(obj) || obj; // normally obj is returned but constructors in JS can return a value

Object.create( Test.prototype )

  1. crear new Object()obj
  2. establecido obj.__proto__enTest.prototype
  3. return obj;

Entonces, básicamente Object.create, no ejecuta el constructor.

Ray Hulha
fuente
@Ray, entonces, usando object.create, ¿tenemos las propiedades de función mencionadas en la función constructora?
@sortednoun siempre y cuando las propiedades sean privadas y no estén especificadas en el prototipo, sí, no se heredarán y no las tendrá en el nuevo objeto (y, agregaría, puede esperar obtener propiedades prototipadas eventuales) desde el padre, justo cuando el constructor padre se ha ejecutado al menos una vez).
Kamafeather
Como con la mayoría de las funciones de constructor, los métodos se definen dentro del objeto devuelto, newbásicamente tiene todas las funciones duplicadas, mientras Object.createque no.
SparK
61

Déjame intentar explicar (más en el blog ):

  1. Cuando se escribe Carconstructor var Car = function(){}, así es como son las cosas internamente: Un diagrama de cadenas prototípicas al crear objetos javascript Tenemos un {prototype}vínculo oculto a Function.prototypeque no es accesible y un prototypeenlace a Car.prototypela que es accesible y tiene un real constructorde Car. Tanto Function.prototype como Car.prototype tienen enlaces ocultos Object.prototype.
  2. Cuando queremos crear dos objetos equivalentes utilizando el newoperador y el createmétodo, tenemos que hacerlo así: Honda = new Car();y Maruti = Object.create(Car.prototype). Un diagrama de cadenas prototípicas para diferentes métodos de creación de objetos. ¿Qué está pasando?

    Honda = new Car();- Cuando crea un objeto como este {prototype}, se apunta a la propiedad oculta Car.prototype. Entonces, aquí, el {prototype}objeto Honda siempre será Car.prototype: no tenemos ninguna opción para cambiar la {prototype}propiedad del objeto. ¿Qué sucede si quiero cambiar el prototipo de nuestro objeto recién creado?
    Maruti = Object.create(Car.prototype)- Cuando crea un objeto como este, tiene una opción adicional para elegir la {prototype}propiedad de su objeto . Si desea Car.prototype como el {prototype}entonces páselo como un parámetro en la función. Si no desea ninguna {prototype}para su objeto entonces puede pasar de nullla siguiente manera: Maruti = Object.create(null).

Conclusión: al utilizar el método Object.create, tiene la libertad de elegir la {prototype}propiedad de su objeto . En new Car();, no tienes esa libertad.

Forma preferida en OO JavaScript:

Supongamos que tenemos dos objetos ay b.

var a = new Object();
var b = new Object();

Ahora, supongamos que atiene algunos métodos a los que btambién quiere acceder. Para eso, requerimos la herencia de objetos ( adebe ser el prototipo de bsolo si queremos acceder a esos métodos). Si verificamos los prototipos de ay bluego descubriremos que comparten el prototipo Object.prototype.

Object.prototype.isPrototypeOf(b); //true
a.isPrototypeOf(b); //false (the problem comes into the picture here).

Problema: queremos un objeto acomo prototipo b, pero aquí creamos un objeto bcon el prototipo Object.prototype. Solución: se introdujo ECMAScript 5 Object.create()para lograr fácilmente dicha herencia. Si creamos un objeto bcomo este:

var b = Object.create(a);

entonces,

a.isPrototypeOf(b);// true (problem solved, you included object a in the prototype chain of object b.)

Entonces, si está haciendo secuencias de comandos orientadas a objetos, entonces Object.create()es muy útil para la herencia.

Anshul
fuente
Entonces, ¿es algo similar a la creación de objetos sin invocación de constructor? Disfrutaremos de todos los beneficios de la clase. La instancia obj de Class también será verdadera. Pero no estamos invocando la función Class a través de new.
Praveen
@Anshul Dijiste que a.isPrototypeOf(b);devolverá falselo correcto, porque ambos Objetos son diferentes y apuntan a una memoria diferente. La forma correcta de hacer esto con el newoperador está aquí. - jsfiddle.net/167onunp .
Sagar Karira
¿Por qué no establecerías la propiedad prototipo de b en a, en lugar de hacerlo?
Amnésico el
Me gustó el artículo en tu blog también. Me ayudó a entender el concepto mucho mejor. Gracias.
steady_daddy
1
La conclusión lo dice todo.
kushalvm
44

Esta:

var foo = new Foo();

y

var foo = Object.create(Foo.prototype);

son bastante parecidos Una diferencia importante es que new Foorealmente ejecuta código de constructor, mientras Object.createque no ejecutará código como

function Foo() {
    alert("This constructor does not run with Object.create");
}

Tenga en cuenta que si usa la versión de dos parámetros de Object.create()entonces puede hacer cosas mucho más poderosas.

Leopd
fuente
1
Gran explicación Podría agregar, usarlo Object.createen su forma más simple como esta le permite omitir funciones de constructor de su código mientras aprovecha la herencia del prototipo.
Ricky Boyce
23

La diferencia es la llamada "herencia pseudoclásica versus prototípica". La sugerencia es usar solo un tipo en su código, no mezclar los dos.

En la herencia pseudoclásica (con el operador "nuevo"), imagine que primero define una pseudoclase y luego crea objetos a partir de esa clase. Por ejemplo, defina una pseudo-clase "Persona", y luego cree "Alice" y "Bob" a partir de "Persona".

En la herencia de prototipos (usando Object.create), crea directamente una persona específica "Alice", y luego crea otra persona "Bob" usando "Alice" como prototipo. No hay "clase" aquí; Todos son objetos.

Internamente, JavaScript utiliza "herencia prototípica"; la forma "pseudoclásica" es solo un poco de azúcar.

Vea este enlace para una comparación de las dos formas.

usuario1931858
fuente
21
function Test(){
    this.prop1 = 'prop1';
    this.prop2 = 'prop2';
    this.func1 = function(){
        return this.prop1 + this.prop2;
    }
};

Test.prototype.protoProp1 = 'protoProp1';
Test.prototype.protoProp2 = 'protoProp2';
var newKeywordTest = new Test();
var objectCreateTest = Object.create(Test.prototype);

/* Object.create   */
console.log(objectCreateTest.prop1); // undefined
console.log(objectCreateTest.protoProp1); // protoProp1 
console.log(objectCreateTest.__proto__.protoProp1); // protoProp1

/* new    */
console.log(newKeywordTest.prop1); // prop1
console.log(newKeywordTest.__proto__.protoProp1); // protoProp1

Resumen:

1) con la newpalabra clave hay dos cosas a tener en cuenta;

a) la función se usa como constructor

b) el function.prototypeobjeto se pasa a la __proto__propiedad ... o donde __proto__no es compatible, es el segundo lugar donde el nuevo objeto busca encontrar propiedades

2) con Object.create(obj.prototype)usted está construyendo un objeto ( obj.prototype) y pasándolo al objeto deseado ... con la diferencia de que ahora los objetos nuevos __proto__también apuntan a obj.prototype (refire por xj9 para eso)

usuario3124360
fuente
15

Variantes de creación de objetos.


Variante 1 : ' new Object () ' -> Constructor de objetos sin argumentos.

var p1 = new Object(); // 'new Object()' create and return empty object -> {}

var p2 = new Object(); // 'new Object()' create and return empty object -> {}

console.log(p1); // empty object -> {}

console.log(p2); // empty object -> {}

// p1 and p2 are pointers to different objects
console.log(p1 === p2); // false

console.log(p1.prototype); // undefined

// empty object which is in fact Object.prototype
console.log(p1.__proto__); // {}

// empty object to which p1.__proto__ points
console.log(Object.prototype); // {}

console.log(p1.__proto__ === Object.prototype); // true

// null, which is in fact Object.prototype.__proto__
console.log(p1.__proto__.__proto__); // null

console.log(Object.prototype.__proto__); // null

ingrese la descripción de la imagen aquí


Variante 2 : ' nuevo Objeto (persona) ' -> Constructor de objetos con argumento.

const person = {
    name: 'no name',
    lastName: 'no lastName',
    age: -1
}

// 'new Object(person)' return 'person', which is pointer to the object ->
//  -> { name: 'no name', lastName: 'no lastName', age: -1 }
var p1 = new Object(person);

// 'new Object(person)' return 'person', which is pointer to the object ->
//  -> { name: 'no name', lastName: 'no lastName', age: -1 }
var p2 = new Object(person);

// person, p1 and p2 are pointers to the same object
console.log(p1 === p2); // true
console.log(p1 === person); // true
console.log(p2 === person); // true

p1.name = 'John'; // change 'name' by 'p1'
p2.lastName = 'Doe'; // change 'lastName' by 'p2'
person.age = 25; // change 'age' by 'person'

// when print 'p1', 'p2' and 'person', it's the same result,
// because the object they points is the same
console.log(p1); // { name: 'John', lastName: 'Doe', age: 25 }
console.log(p2); // { name: 'John', lastName: 'Doe', age: 25 }
console.log(person); // { name: 'John', lastName: 'Doe', age: 25 }

ingrese la descripción de la imagen aquí


Variante 3.1 : ' Object.create (persona) '. Use Object.create con el objeto simple 'persona'. 'Object.create (persona)' creará (y devolverá) un nuevo objeto vacío y agregará la propiedad '__proto__' al mismo nuevo objeto vacío. Esta propiedad '__proto__' apuntará al objeto 'persona'.

const person = {
        name: 'no name',
        lastName: 'no lastName',
        age: -1,
        getInfo: function getName() {
           return `${this.name} ${this.lastName}, ${this.age}!`;
    }
}

var p1 = Object.create(person);

var p2 = Object.create(person);

// 'p1.__proto__' and 'p2.__proto__' points to
// the same object -> 'person'
// { name: 'no name', lastName: 'no lastName', age: -1, getInfo: [Function: getName] }
console.log(p1.__proto__);
console.log(p2.__proto__);
console.log(p1.__proto__ === p2.__proto__); // true

console.log(person.__proto__); // {}(which is the Object.prototype)

// 'person', 'p1' and 'p2' are different
console.log(p1 === person); // false
console.log(p1 === p2); // false
console.log(p2 === person); // false

// { name: 'no name', lastName: 'no lastName', age: -1, getInfo: [Function: getName] }
console.log(person);

console.log(p1); // empty object - {}

console.log(p2); // empty object - {}

// add properties to object 'p1'
// (properties with the same names like in object 'person')
p1.name = 'John';
p1.lastName = 'Doe';
p1.age = 25;

// add properties to object 'p2'
// (properties with the same names like in object 'person')
p2.name = 'Tom';
p2.lastName = 'Harrison';
p2.age = 38;

// { name: 'no name', lastName: 'no lastName', age: -1, getInfo: [Function: getName] }
console.log(person);

// { name: 'John', lastName: 'Doe', age: 25 }
console.log(p1);

// { name: 'Tom', lastName: 'Harrison', age: 38 }
console.log(p2);

// use by '__proto__'(link from 'p1' to 'person'),
// person's function 'getInfo'
console.log(p1.getInfo()); // John Doe, 25!

// use by '__proto__'(link from 'p2' to 'person'),
// person's function 'getInfo'
console.log(p2.getInfo()); // Tom Harrison, 38!

ingrese la descripción de la imagen aquí


Variante 3.2 : ' Object.create (Object.prototype) '. Use Object.create con el objeto incorporado -> 'Object.prototype'. 'Object.create (Object.prototype)' creará (y devolverá) un nuevo objeto vacío y agregará la propiedad '__proto__' al mismo nuevo objeto vacío. Esta propiedad '__proto__' apuntará al objeto 'Object.prototype'.

// 'Object.create(Object.prototype)' :
// 1. create and return empty object -> {}.
// 2. add to 'p1' property '__proto__', which is link to 'Object.prototype'
var p1 = Object.create(Object.prototype);

// 'Object.create(Object.prototype)' :
// 1. create and return empty object -> {}.
// 2. add to 'p2' property '__proto__', which is link to 'Object.prototype'
var p2 = Object.create(Object.prototype);

console.log(p1); // {}

console.log(p2); // {}

console.log(p1 === p2); // false

console.log(p1.prototype); // undefined

console.log(p2.prototype); // undefined

console.log(p1.__proto__ === Object.prototype); // true

console.log(p2.__proto__ === Object.prototype); // true

ingrese la descripción de la imagen aquí


Variante 4 : ' nueva SomeFunction () '

// 'this' in constructor-function 'Person'
// represents a new instace,
// that will be created by 'new Person(...)'
// and returned implicitly
function Person(name, lastName, age) {

    this.name = name;
    this.lastName = lastName;
    this.age = age;

    //-----------------------------------------------------------------
    // !--- only for demonstration ---
    // if add function 'getInfo' into
    // constructor-function 'Person',
    // then all instances will have a copy of the function 'getInfo'!
    //
    // this.getInfo: function getInfo() {
    //  return this.name + " " + this.lastName + ", " + this.age + "!";
    // }
    //-----------------------------------------------------------------
}

// 'Person.prototype' is an empty object
// (before add function 'getInfo')
console.log(Person.prototype); // Person {}

// With 'getInfo' added to 'Person.prototype',
// instances by their properties '__proto__',
// will have access to the function 'getInfo'.
// With this approach, instances not need
// a copy of the function 'getInfo' for every instance.
Person.prototype.getInfo = function getInfo() {
    return this.name + " " + this.lastName + ", " + this.age + "!";
}

// after function 'getInfo' is added to 'Person.prototype'
console.log(Person.prototype); // Person { getInfo: [Function: getInfo] }

// create instance 'p1'
var p1 = new Person('John', 'Doe', 25);

// create instance 'p2'
var p2 = new Person('Tom', 'Harrison', 38);

// Person { name: 'John', lastName: 'Doe', age: 25 }
console.log(p1);

// Person { name: 'Tom', lastName: 'Harrison', age: 38 }
console.log(p2);

// 'p1.__proto__' points to 'Person.prototype'
console.log(p1.__proto__); // Person { getInfo: [Function: getInfo] }

// 'p2.__proto__' points to 'Person.prototype'
console.log(p2.__proto__); // Person { getInfo: [Function: getInfo] }

console.log(p1.__proto__ === p2.__proto__); // true

// 'p1' and 'p2' points to different objects(instaces of 'Person')
console.log(p1 === p2); // false

// 'p1' by its property '__proto__' reaches 'Person.prototype.getInfo' 
// and use 'getInfo' with 'p1'-instance's data
console.log(p1.getInfo()); // John Doe, 25!

// 'p2' by its property '__proto__' reaches 'Person.prototype.getInfo' 
// and use 'getInfo' with 'p2'-instance's data
console.log(p2.getInfo()); // Tom Harrison, 38!

ingrese la descripción de la imagen aquí

Ted
fuente
Resumen agradable Gracias. ¡Me ayudó hoy!
Anandaraja_Srinivasan
11

Internamente Object.createhace esto:

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

La sintaxis simplemente elimina la ilusión de que JavaScript usa herencia clásica.

xj9
fuente
25
El Object.createmétodo ECMAScript 5 hace mucho más que eso, puede definir propiedades por descriptores de propiedad y puede crear un objeto que no herede de nada ( Object.create(null);), este tipo de calzas se debe evitar porque realmente no puede emular eso comportamiento en ES3. Más información
CMS
De acuerdo con @CMS pero en general, es un polyfill simple para Object.create.
V. Kovpak
10

De acuerdo con esta respuesta y con esta new palabra clave de video, lo siguiente es lo siguiente:

  1. Crea un nuevo objeto.

  2. Vincula un nuevo objeto a la función constructora ( prototype).

  3. Hace thispunto variable al nuevo objeto.

  4. Ejecuta la función de constructor utilizando el nuevo objeto y la ejecución implícita return this;

  5. Asigna el nombre de la función del constructor a la propiedad del nuevo objeto constructor.

Object.createrealiza solo 1sty 2ndpasos !!!

V. Kovpak
fuente