Compreendendo a diferença entre Object.create () e new SomeFunction ()

392

Recentemente, deparei com o Object.create()método em JavaScript e estou tentando deduzir como ele é diferente de criar uma nova instância de um objeto new SomeFunction()e quando você deseja usar um sobre o outro.

Considere o seguinte exemplo:

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 o mesmo comportamento é observado nos dois casos. Parece-me que as principais diferenças entre esses dois cenários são:

  • O objeto usado na Object.create()verdade forma o protótipo do novo objeto, enquanto que nas new Function()propriedades / funções declaradas não formam o protótipo.
  • Você não pode criar fechamentos com a Object.create()sintaxe como faria com a sintaxe funcional. Isso é lógico, considerando o escopo do tipo lexical (vs block) do JavaScript.

As afirmações acima estão corretas? E estou faltando alguma coisa? Quando você usaria um sobre o outro?

EDIT: link para a versão jsfiddle do exemplo de código acima: http://jsfiddle.net/rZfYL/

Matt
fonte

Respostas:

246

O objeto usado no Object.create realmente forma o protótipo do novo objeto, onde, como no novo Function (), as propriedades / funções declaradas não formam o protótipo.

Sim, Object.createcria um objeto que herda diretamente do passado como seu primeiro argumento.

Com as funções do construtor, o objeto recém-criado herda do protótipo do construtor, por exemplo:

var o = new SomeConstructor();

No exemplo acima, oherda diretamente de SomeConstructor.prototype.

Há uma diferença aqui: Object.createvocê pode criar um objeto que não herda de nada Object.create(null);; por outro lado, se você definir SomeConstructor.prototype = null;o objeto recém-criado, herdará Object.prototype.

Você não pode criar fechamentos com a sintaxe Object.create como faria com a sintaxe funcional. Isso é lógico, considerando o escopo do tipo lexical (vs block) do JavaScript.

Bem, você pode criar fechamentos, por exemplo, usando o argumento de descritores de propriedades:

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

o.foo; // "foobar"

Observe que estou falando do Object.createmétodo ECMAScript 5th Edition , não do calço de Crockford.

O método está começando a ser implementado nativamente nos navegadores mais recentes, verifique esta tabela de compatibilidade .

CMS
fonte
2
@CMS 2 perguntas. 1) A cadeia de escopo em Object.create (null) ainda é finalizada no escopo global (como 'janela' em um navegador) ou é finalizada por si mesma? 2) Ainda não está claro para mim por que o Object.create foi introduzido (por exemplo, qual recurso estava faltando e que foi abordado?) E por que alguém o usaria em vez da nova Function ();
Matt
9
@ Matt, 1) a cadeia de escopo não é realmente um conceito relacionado aqui, a cadeia de escopo está relacionada à resolução do identificador , por exemplo: como foo;é resolvido no ambiente lexical atual . 2) Para fornecer uma maneira fácil de implementar herança, é uma construção realmente poderosa. Na IMO, eu o usaria porque é realmente simples e leve, mas para o código de produção, ainda precisamos esperar um pouco até que o ES5 seja amplamente suportado. Sobre recursos ausentes, o fato de criar um objeto "intocada", Object.create(null);estava faltando, é realmente útil para implementar confiável de hash-table-como objetos ...
CMS
Obrigado @CMS. Então, simplesmente quando você cria um objeto usando 'Object.create', você pode selecionar o objeto que deve ser seu protótipo.
Anshul 02/09
@ CMS OK, Object.create(null)significa que você não precisa usar hasOwnProperty()porcaria ao iterar porque ele não herda nenhum ??? Eu gosto disso - obrigado. Claro, todo mundo ainda vai fazer, hasOwnPropertyjá que nem todo mundo vai usar, Object.create(null)então não tenho certeza de que seja um benefício real ... Até agora, encontrei os outros "benefícios" de Object.create()não ser convincente.
user949300
425

Muito simplesmente, new Xestá Object.create(X.prototype)executando adicionalmente a constructorfunção. (E dando a constructorchance ao returnobjeto real que deve ser o resultado da expressão em vez de this.)

É isso aí. :)

O restante das respostas é apenas confuso, porque aparentemente ninguém mais lê a definição de newnenhum dos dois. ;)

Evi1M4chine
fonte
23
+1 Simplicidade e clareza! (Embora o Object.create (null) pareça uma boa opção - talvez deva mencionar isso).
user949300
mantê-lo simples, que é o caminho a percorrer
Bill
Isso só deixa a questão de "esperar, portanto, funções têm protótipos também ? Qual é a relação entre aqueles e objetos protótipos?"
Qwertie
3
@ Qwertie: Em JS, tudo é um objeto. :) Eles copiaram isso do Java, que copiou do SmallTalk, que foi até o fim com ele. É um bom caso de "emergência", facilitando a vida em geral.
Evi1M4chine
@ Evi1M4chine, na verdade, em Java, funções não são objetos (e também não são primitivas) ... e objetos não têm protótipos, portanto a comparação parece inadequada. O fato de o JS funcionar de maneira diferente de outras linguagens OO populares é uma importante fonte de confusão (e não ajuda o fato de os navegadores não fornecerem uma maneira fácil de visualizar a rede de objetos, incluindo funções e protótipos). PS Eu encontrei este link útil: davidwalsh.name/javascript-objects-deconstruction
Qwertie
204

Aqui estão as etapas que ocorrem internamente para as duas chamadas:
(Dica: a única diferença está na etapa 3)


new Test():

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

Object.create( Test.prototype )

  1. criar new Object()obj
  2. definido obj.__proto__comoTest.prototype
  3. return obj;

Então, basicamente Object.create, não executa o construtor.

Ray Hulha
fonte
@ Ray então usando object.create nós fonte temos as propriedades da função mencionada na função construtora?
@sortednoun contanto que as propriedades sejam privadas e não especificadas no protótipo, sim, elas não serão herdadas e você não as terá no novo objeto (e, eu acrescentaria, você pode esperar obter eventuais propriedades de prototipagem do pai, apenas quando o construtor pai tiver sido executado pelo menos uma vez).
Kamafeather
Como na maioria das funções de construtor, os métodos são definidos no objeto retornado, newbasicamente todas as funções são duplicadas, enquanto Object.createnão.
Spark em
61

Deixe-me tentar explicar (mais no Blog ):

  1. Quando você escreve Carconstrutor var Car = function(){}, é assim que as coisas são internamente: Um diagrama de cadeias prototípicas ao criar objetos javascript Nós temos um {prototype}link escondido para Function.prototypeque não é acessível e um prototypelink para Car.prototypeque é acessível e tem um real constructorde Car. Function.prototype e Car.prototype têm links ocultos para Object.prototype.
  2. Quando queremos criar dois objetos equivalentes usando o newoperador e o createmétodo, devemos fazê-lo assim: Honda = new Car();e Maruti = Object.create(Car.prototype). Um diagrama de cadeias de protótipos para diferentes métodos de criação de objetos O que está acontecendo?

    Honda = new Car();- Quando você cria um objeto como este, a {prototype}propriedade oculta é apontada Car.prototype. Então, aqui, o {prototype}objeto Honda sempre será Car.prototype- não temos opção de alterar a {prototype}propriedade do objeto. E se eu quiser alterar o protótipo do nosso objeto recém-criado?
    Maruti = Object.create(Car.prototype)- Ao criar um objeto como esse, você tem uma opção extra para escolher a {prototype}propriedade do seu objeto . Se você deseja Car.prototype como o {prototype}, passe-o como um parâmetro na função Se você não quer nenhum {prototype}para o seu objeto, em seguida, você pode passar nullassim: Maruti = Object.create(null).

Conclusão - Ao usar o método, Object.createvocê tem a liberdade de escolher sua {prototype}propriedade de objeto . Em new Car();, você não tem essa liberdade.

Maneira preferida em OO JavaScript:

Suponha que temos dois objetos ae b.

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

Agora, suponha que atenha alguns métodos que btambém desejem acessar. Para isso, exigimos herança de objeto ( adeve ser o protótipo bsomente se quisermos acessar esses métodos). Se verificarmos os protótipos de ae bdepois descobriremos que eles compartilham o protótipo Object.prototype.

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

Problema - queremos objeto acomo o protótipo de b, mas aqui criamos objeto bcom o protótipo Object.prototype. Solução - introduzido o ECMAScript 5 Object.create(), para obter facilmente essa herança. Se criarmos um objeto bcomo este:

var b = Object.create(a);

então,

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

Portanto, se você estiver executando scripts orientados a objetos, Object.create()será muito útil para herança.

Anshul
fonte
Então, é um pouco semelhante à criação de objetos sem a chamada do construtor? Desfrutaremos de todos os benefícios da classe. A instância obj da Class também será verdadeira. Mas não estamos invocando a função Class via new.
Praveen
@ Anshul Você disse que a.isPrototypeOf(b);retornará o falseque é certo, porque os dois objetos são diferentes e apontam para uma memória diferente. A maneira correta de fazer isso com o newoperador está aqui. - jsfiddle.net/167onunp .
Sagar Karira
Por que você não definiu a propriedade prototype de b como a, em vez de fazer isso?
Amnestic
Gostei do artigo em seu blog também. Me ajudou a entender o conceito muito melhor. Obrigado.
steady_daddy
11
A conclusão diz tudo.
kushalvm
44

Este:

var foo = new Foo();

e

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

são bem parecidos. Uma diferença importante é que, new Foona verdade, ele executa código de construtor, enquanto Object.createque não executa código como

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

Observe que se você usar a versão de dois parâmetros Object.create(), poderá fazer coisas muito mais poderosas.

Leopd
fonte
11
Ótima explicação. Devo acrescentar que o uso Object.createem sua forma mais simples como essa permite que você omita as funções construtoras do seu código enquanto tira proveito da herança do protótipo.
Ricky Boyce
23

A diferença é a chamada "herança pseudoclássica vs. prototípica". A sugestão é usar apenas um tipo no seu código, não misturando os dois.

Na herança pseudoclássica (com o operador "novo"), imagine que você primeiro defina uma pseudo-classe e depois crie objetos a partir dessa classe. Por exemplo, defina uma pseudo classe "Pessoa" e crie "Alice" e "Bob" a partir de "Pessoa".

Na herança prototípica (usando Object.create), você cria diretamente uma pessoa específica "Alice" e, em seguida, cria outra pessoa "Bob" usando "Alice" como protótipo. Não há "classe" aqui; todos são objetos.

Internamente, o JavaScript usa "herança prototípica"; o caminho "pseudoclássico" é apenas um pouco de açúcar.

Veja este link para uma comparação das duas maneiras.

user1931858
fonte
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

Resumo:

1) com a newpalavra-chave, há duas coisas a serem observadas;

a) a função é usada como construtor

b) o function.prototypeobjeto é passado para a __proto__propriedade ... ou onde __proto__não é suportado, é o segundo lugar em que o novo objeto procura encontrar propriedades

2) com Object.create(obj.prototype)você construindo um objeto ( obj.prototype) e passando-o para o objeto pretendido .. com a diferença que agora o novo objeto __proto__também está apontando para obj.prototype (consulte xj9 para isso)

user3124360
fonte
15

Variantes de criação de objeto.


Variante 1 : ' new Object () ' -> Construtor de objetos sem 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

insira a descrição da imagem aqui


Variante 2 : ' new Object (person) ' -> Construtor de objetos com 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 }

insira a descrição da imagem aqui


Variante 3.1 : ' Object.create (person) '. Use Object.create com o objeto simples 'person'. 'Object.create (person)' criará (e retornará) um novo objeto vazio e adicionará a propriedade '__proto__' ao mesmo novo objeto vazio. Esta propriedade '__proto__' apontará para o objeto 'pessoa'.

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!

insira a descrição da imagem aqui


Variante 3.2 : ' Object.create (Object.prototype) '. Use Object.create com o objeto interno -> 'Object.prototype'. 'Object.create (Object.prototype)' criará (e retornará) um novo objeto vazio e adicionará a propriedade '__proto__' ao mesmo novo objeto vazio. Esta propriedade '__proto__' apontará para o 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

insira a descrição da imagem aqui


Variante 4 : ' new 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!

insira a descrição da imagem aqui

Ted
fonte
Resumo agradável. Obrigado. Me ajudou hoje !!
Anandaraja_Srinivasan
11

Internamente, Object.createfaz isso:

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

A sintaxe apenas tira a ilusão de que o JavaScript usa a herança clássica.

xj9
fonte
25
O Object.createmétodo ECMAScript 5 faz muito mais do que isso, você pode definir propriedades por descritores de propriedades e criar um objeto que não herda de nada ( Object.create(null);); esse tipo de calço deve ser evitado porque você não pode emular comportamento no ES3. Mais informações
CMS
Concorde com o @CMS, mas, em geral, é um polyfill simples Object.create.
V. Kovpak
10

De acordo com esta resposta e com este vídeo, a new palavra-chave faz o seguinte:

  1. Cria um novo objeto.

  2. Vincula um novo objeto à função construtora ( prototype).

  3. Faz thisponto variável para o novo objeto.

  4. Executa a função construtora usando o novo objeto e a execução implícita return this;

  5. Atribui o nome da função do construtor à propriedade do novo objeto constructor.

Object.createexecuta apenas 1ste 2ndetapas !!!

V. Kovpak
fonte