Auto-referências em literais / inicializadores de objetos

705

Existe alguma maneira de obter algo como o seguinte para trabalhar em JavaScript?

var foo = {
    a: 5,
    b: 6,
    c: this.a + this.b  // Doesn't work
};

No formulário atual, esse código obviamente gera um erro de referência, pois thisnão se refere a foo. Mas é Existe alguma maneira de ter valores nas propriedades de um objeto de literais dependem de outras propriedades declarado anteriormente?

kpozin
fonte

Respostas:

744

Bem, a única coisa que posso lhe dizer é getter :

var foo = {
  a: 5,
  b: 6,
  get c() {
    return this.a + this.b;
  }
}

console.log(foo.c) // 11

Essa é uma extensão sintática introduzida pela especificação do ECMAScript 5th Edition. A sintaxe é suportada pela maioria dos navegadores modernos (incluindo o IE9).

CMS
fonte
32
Resposta muito útil. Mais informações sobre 'get' podem ser encontradas aqui: developer.mozilla.org/en-US/docs/JavaScript/Reference/Operators/…
jake
5
@FaustoR. Sim.
Charlie Martin
48
Cuidado com esta solução, se os valores de foo.aou foo.bforem alterados, o valor de foo.ctambém mudará no sincronismo. Isso pode ou não ser o necessário.
HBP
8
Observe que thisse liga ao objeto aninhado mais profundo. Por exemplo:... x: { get c () { /*this is x, not foo*/ } } ...
Z. Khullah 18/04/19
3
Para completar a minha afirmação acima, uma vez que foohoje vem sendo declarado como uma variável e csó será avaliada no momento em que é invocado, usando foointerior cirá funcionar, ao contrário de this(ser embora cuidado)
Z. Khullah
316

Você poderia fazer algo como:

var foo = {
   a: 5,
   b: 6,
   init: function() {
       this.c = this.a + this.b;
       return this;
   }
}.init();

Isso seria algum tipo de inicialização única do objeto.

Observe que você está realmente atribuindo o valor de retorno de init()para foo, portanto, você precisa return this.

Felix Kling
fonte
95
você também pode delete this.initantes, return thispara que foonão seja poluído.
Billy Moon
15
@BillyMoon: Sim, de fato, embora isso tenha impacto no desempenho de todos os acessos a propriedades subseqüentes nesse objeto, em muitos mecanismos (V8, por exemplo).
TJ Crowder
8
@ MuhammadUmer: Não tenho certeza de como as classes ES6 são relevantes para a questão.
Felix Kling
8
@ MuhammadUmer: classes são apenas açúcar sintático para funções de construtor, então elas realmente não fornecem nada de novo. De qualquer forma, o foco principal desta questão são literais de objetos.
Felix Kling
3
@akantoword: Ótimo :) como os literais de objetos são uma expressão única, a init()chamada foi anexada diretamente ao literal para mantê-lo como uma expressão única. Mas é claro que você pode chamar a função separadamente, se desejar.
Felix Kling
181

A resposta óbvia e simples está faltando, portanto, para ser completo:

Mas é Existe alguma maneira de ter valores nas propriedades de um objeto de literais dependem de outras propriedades declarado anteriormente?

Não. Todas as soluções aqui adiam para depois que o objeto é criado (de várias maneiras) e depois atribuem a terceira propriedade. A maneira mais simples é fazer isso:

var foo = {
    a: 5,
    b: 6
};
foo.c = foo.a + foo.b;

Todos os outros são apenas maneiras mais indiretas de fazer a mesma coisa. (O Felix's é particularmente inteligente, mas requer a criação e destruição de uma função temporária, adicionando complexidade; e deixa uma propriedade extra no objeto ou [se você tiver deleteessa propriedade] afeta o desempenho dos acessos subsequentes à propriedade nesse objeto.)

Se você precisar que tudo esteja dentro de uma expressão, poderá fazer isso sem a propriedade temporária:

var foo = function(o) {
    o.c = o.a + o.b;
    return o;
}({a: 5, b: 6});

Ou, é claro, se você precisar fazer isso mais de uma vez:

function buildFoo(a, b) {
    var o = {a: a, b: b};
    o.c = o.a + o.b;
    return o;
}

então onde você precisa usá-lo:

var foo = buildFoo(5, 6);
TJ Crowder
fonte
Para minha própria sanidade, estou tentando encontrar uma espécie de documentação oficial que diz basicamente a mesma coisa - que um objeto thisestá disponível apenas para métodos desse objeto e nenhum outro tipo de propriedades. Alguma idéia de onde eu poderia encontrar isso? Obrigado!
David Kennell 5/05
1
@ DavidKennell: Não é mais oficial que a especificação . :-) Você provavelmente começaria aqui e continuaria. É uma linguagem bastante difícil, mas basicamente você verá nas várias subcláusulas da Avaliação de Definição de Propriedade que o objeto não está disponível para as operações que determinam os valores dos inicializadores de propriedade.
TJ Crowder
Não consigo ver os resultados do navegador aqui , mas esse não é mais o caso, certo? No meu ambiente, a v8: deleteé 10% mais rápida e a lagartixa: deleteé apenas 1% mais lenta.
TheMaster
1
@ TheMaster - Sim, acho que o BrowserScope não é mais uma coisa. Parece que excluir não é tão ruim como costumava ser, pelo menos não no V8 (Chrome, etc.) ou no SpiderMonkey. Ainda mais lento, mas apenas um pouquinho, e essas coisas são muito rápidas hoje em dia.
TJ Crowder
63

Instancie simplesmente uma função anônima:

var foo = new function () {
    this.a = 5;
    this.b = 6;
    this.c = this.a + this.b;
};
zzzzBov
fonte
1
@ Bergi, por quê? Porque alguém pode instanciar outro do mesmo objeto a partir dele? Não é como eles não podem simplesmente clonar um objeto literal. Não é diferente de passar um argumento como, new Point(x, y)exceto que a função não é nomeada para reutilização.
precisa saber é o seguinte
1
@zzzzBov: É claro que eles podem clonar o objeto, mas comparada a uma solução IEFE (como na resposta de TJCrowder), sua solução vaza a função construtora e cria um objeto protótipo supérfluo.
Bergi
5
@zzzzBov: Basta usar o var foo = function() { this.…; return this; }.call({});que não é sintaticamente muito diferente, mas semanticamente saudável.
Bergi
1
@ Bergi, se você acha que é tão importante, por que não adicionar sua própria resposta à mistura?
precisa saber é o seguinte
3
Você conseguiu isso. Na verdade, eu não percebi a newpalavra - chave.
Randy
26

Agora no ES6, você pode criar propriedades em cache lento. No primeiro uso, a propriedade avalia uma vez para se tornar uma propriedade estática normal. Resultado: na segunda vez que a sobrecarga da função matemática é ignorada.

A mágica está no caos.

const foo = {
    a: 5,
    b: 6,
    get c() {
        delete this.c;
        return this.c = this.a + this.b
    }
};

Na seta, o captador thispega o escopo lexical circundante .

foo     // {a: 5, b: 6}
foo.c   // 11
foo     // {a: 5, b: 6 , c: 11}  
voscausa
fonte
1
O es5 também possui propriedades que você só precisa usar Object.defineProperty(foo, 'c', {get:function() {...}})para defini-las. Isso é facilmente feito de uma maneira discreta em uma fábrica como esta. Obviamente, se você pode usar o getaçúcar, é mais legível, mas a capacidade existe.
Aluan Haddad
21

Algum fechamento deve lidar com isso;

var foo = function() {
    var a = 5;
    var b = 6;
    var c = a + b;

    return {
        a: a,
        b: b,
        c: c
    }
}();

Todas as variáveis ​​declaradas em foosão privadas foo, como seria de esperar em qualquer declaração de função e, como todas elas estão no escopo, todas elas têm acesso uma à outra sem precisar se referir this, da mesma forma que você esperaria com uma função. A diferença é que essa função retorna um objeto que expõe as variáveis ​​privadas e atribui esse objeto foo. No final, você retorna apenas a interface que deseja expor como um objeto com a return {}instrução

A função é então executada no final com a ()qual faz com que todo o objeto foo seja avaliado, todas as variáveis ​​em instanciadas e o objeto de retorno adicionados como propriedades de foo().

Henry Wrightson
fonte
14
É confuso e enganoso chamar isso de "fechamento". Embora as opiniões diferam quanto ao significado exato do retorno de um valor de objeto de uma função, isso não constitui um fechamento no livro de ninguém.
16

Você poderia fazer assim

var a, b
var foo = {
    a: a = 5,
    b: b = 6,
    c: a + b
}

Esse método se mostrou útil quando tive que me referir ao objeto em que uma função foi originalmente declarada. A seguir, é apresentado um exemplo mínimo de como eu o usei:

function createMyObject() {
    var count = 0, self
    return {
        a: self = {
            log: function() {
                console.log(count++)
                return self
            }
        }
    }
}

Ao definir self como o objeto que contém a função de impressão, você permite que a função se refira a esse objeto. Isso significa que você não precisará 'vincular' a função de impressão a um objeto se precisar passá-la para outro lugar.

Se preferir, use thiscomo ilustrado abaixo

function createMyObject() {
    var count = 0
    return {
        a: {
            log: function() {
                console.log(count++)
                return this
            }
        }
    }
}

Em seguida, o código a seguir registrará 0, 1, 2 e, em seguida, dará um erro

var o = createMyObject()
var log = o.a.log
o.a.log().log() // this refers to the o.a object so the chaining works
log().log() // this refers to the window object so the chaining fails!

Ao usar o método self, você garante que a impressão sempre retornará o mesmo objeto, independentemente do contexto em que a função é executada. O código acima funcionará perfeitamente e registrará 0, 1, 2 e 3 ao usar a versão própria do createMyObject().

davedambonite
fonte
3
Bons exemplos, exceto que você deixou de fora todos os pontos e vírgulas.
1
Sem ponto e vírgula - está tudo bem mesmo !
Kerem Baydoğan
9

Para concluir, no ES6, temos aulas (suportadas no momento em que escrevemos isso apenas pelos navegadores mais recentes, mas disponíveis no Babel, TypeScript e outros transpilers)

class Foo {
  constructor(){
    this.a = 5;
    this.b = 6;
    this.c = this.a + this.b;
  }  
}

const foo = new Foo();
monzonj
fonte
7

Você pode fazer isso usando o padrão do módulo. Assim como:

var foo = function() {
  var that = {};

  that.a = 7;
  that.b = 6;

  that.c = function() {
    return that.a + that.b;
  }

  return that;
};
var fooObject = foo();
fooObject.c(); //13

Com esse padrão, você pode instanciar vários objetos foo de acordo com sua necessidade.

http://jsfiddle.net/jPNxY/1/

Rafael Rocha
fonte
2
Este não é um exemplo do padrão do módulo, apenas uma função. Se a última linha da definição foo fosse }();, ela executaria e retornaria automaticamente um objeto, não uma função. Além disso, foo.cé uma função, portanto, escrever nele trechos que funcionem e a próxima chamada via fooObject.c()falhará. Talvez esse violino esteja mais próximo do que você procura (também é um singleton, não projetado para ser instanciado).
Hollister
2
"O padrão do módulo foi originalmente definido como uma maneira de fornecer encapsulamento público e privado para as classes na engenharia de software convencional". From: Aprendendo JavaScript Design Patterns . Esse objeto segue o padrão do módulo descrito acima, mas talvez não seja o melhor para explicar isso, porque não mostra propriedades / métodos públicos e privados. Este jsfiddle.net/9nnR5/2 é o mesmo objeto com propriedades / métodos públicos e privados. Assim, ambos estão seguindo este padrão
Rafael Rocha
6

Existem várias maneiras de conseguir isso; isto é o que eu usaria:

function Obj() {
 this.a = 5;
 this.b = this.a + 1;
 // return this; // commented out because this happens automatically
}

var o = new Obj();
o.b; // === 6
ken
fonte
2
Isso funciona, mas elimina as vantagens da notação literal do objeto.
kpozin
É verdade, desculpe, eu perdi a tag literal de objeto originalmente. Na maioria das vezes, uso apenas literais de objeto para estruturas de dados e, sempre que desejar uma lógica adicional (que pode se parecer com uma classe), crio o objeto como resultado de uma função por esse motivo.
ken
6

apenas por uma questão de pensamento - coloque as propriedades do objeto fora de uma linha do tempo:

var foo = {
    a: function(){return 5}(),
    b: function(){return 6}(),
    c: function(){return this.a + this.b}
}

console.log(foo.c())

também existem respostas melhores acima . Foi assim que modifiquei o código de exemplo que você questionou.

ATUALIZAR:

var foo = {
    get a(){return 5},
    get b(){return 6},
    get c(){return this.a + this.b}
}
// console.log(foo.c);
animaacija
fonte
2
Em ES6 você pode fazer esta abordagem geral muito mais elegante: var foo = { get a(){return 5}, get b(){return 6}, get c(){return this.a + this.b} }agora você pode apenas fazer foo.cem vez de foo.c():) (Sinta-se livre para colar isso em sua resposta de modo a formatação é melhor!)
5

Criar nova função no literal do seu objeto e chamar um construtor parece um desvio radical do problema original e é desnecessário.

Você não pode fazer referência a uma propriedade irmão durante a inicialização literal do objeto.

var x = { a: 1, b: 2, c: a + b } // not defined 
var y = { a: 1, b: 2, c: y.a + y.b } // not defined 

A solução mais simples para propriedades calculadas é a seguir (sem heap, sem funções, sem construtor):

var x = { a: 1, b: 2 };

x.c = x.a + x.b; // apply computed property
Rick O'Shea
fonte
3

As outras respostas postadas aqui são melhores, mas aqui está uma alternativa que:

  • Define o valor na inicialização (não é um getter, ou derivado, etc.)
  • Não requer nenhum tipo init()ou código fora do literal do objeto
  • É um objeto literal e não uma função de fábrica ou outro mecanismo de criação de objetos.
  • Não deve ter nenhum impacto no desempenho (exceto na inicialização)

Funções anônimas auto-executáveis ​​e armazenamento de janela

var foo = {
    bar:(function(){
        window.temp = "qwert";
        return window.temp;
    })(),
    baz: window.temp
};

O pedido é garantido ( barantes baz).

Polui window, é claro, mas não consigo imaginar alguém escrevendo um script que exija window.temppersistência. Talvez tempMyAppse você é paranóico.

Também é feio, mas ocasionalmente útil. Um exemplo é quando você está usando uma API com condições rígidas de inicialização e não deseja refatorar, para que o escopo esteja correto.

E está seco, é claro.

DanielST
fonte
3

A chave para tudo isso é o escopo .

Você precisa encapsular o "pai" (objeto pai) da propriedade que deseja definir como seu próprio objeto instanciado e, em seguida, pode fazer referências às propriedades dos irmãos usando a palavra-chave this

É muito, muito importante lembrar que, se você se referir thissem fazê-lo primeiro, thisse referirá ao escopo externo ... que será o windowobjeto.

var x = 9   //this is really window.x
var bar = {
  x: 1,
  y: 2,
  foo: new function(){
    this.a = 5, //assign value
    this.b = 6,
    this.c = this.a + this.b;  // 11
  },
  z: this.x   // 9 (not 1 as you might expect, b/c *this* refers `window` object)
};
Comunidade
fonte
2
Isso nem é JS válido.
2

se seu objeto é escrito como uma função que retorna um objeto E você usa os métodos de atributo de objeto do ES6, é possível:

const module = (state) => ({
  a: 1,
  oneThing() {
    state.b = state.b + this.a
  },
  anotherThing() {
    this.oneThing();
    state.c = state.b + this.a
  },
});

const store = {b: 10};
const root = module(store);

root.oneThing();
console.log(store);

root.anotherThing();
console.log(store);

console.log(root, Object.keys(root), root.prototype);
daviestar
fonte
2

Eu uso o seguinte código como alternativa e funciona. E a variável também pode ser matriz. (@ Fausto R.)

var foo = {
  a: 5,
  b: 6,
  c: function() {
    return this.a + this.b;
  },

  d: [10,20,30],
  e: function(x) {
    this.d.push(x);
    return this.d;
  }
};
foo.c(); // 11
foo.e(40); // foo.d = [10,20,30,40]
Indiana
fonte
2

Aqui está uma maneira elegante do ES6:

var foo = (o => ({
    ...o,
    c: o.a + o.b
  }))({
    a: 5,
    b: 6
  });
  
console.log(foo);

Eu o uso para fazer algo assim:

const constants = Object.freeze(
  (_ => ({
    ..._,
    flag_data: {
      [_.a_flag]: 'foo',
      [_.b_flag]: 'bar',
      [_.c_flag]: 'oof'
    }
  }))({
    a_flag: 5,
    b_flag: 6,
    c_flag: 7,
  })
);

console.log(constants.flag_data[constants.b_flag]);

UDrake
fonte
1

Que tal essa solução também funcionará com objetos aninhados com matriz

      Object.prototype.assignOwnProVal
     = function (to,from){ 
            function compose(obj,string){ 
                var parts = string.split('.'); 
                var newObj = obj[parts[0]]; 
                if(parts[1]){ 
                    parts.splice(0,1);
                    var newString = parts.join('.'); 
                    return compose(newObj,newString); 
                } 
                return newObj; 
            } 
            this[to] = compose(this,from);
     } 
     var obj = { name : 'Gaurav', temp : 
                  {id : [10,20], city:
                        {street:'Brunswick'}} } 
     obj.assignOwnProVal('street','temp.city.street'); 
     obj.assignOwnProVal('myid','temp.id.1');
Gaurav
fonte
0

Jogando em uma opção desde que eu não vi esse cenário exato coberto. Se você não deseja catualizar quando aou batualizar, um ES6 IIFE funciona bem.

var foo = ((a,b) => ({
    a,
    b,
    c: a + b
}))(a,b);

Para minhas necessidades, eu tenho um objeto que se relaciona com uma matriz que acabará sendo usada em um loop, então eu só quero calcular algumas configurações comuns uma vez, então é isso que eu tenho:

  let processingState = ((indexOfSelectedTier) => ({
      selectedTier,
      indexOfSelectedTier,
      hasUpperTierSelection: tiers.slice(0,indexOfSelectedTier)
                             .some(t => pendingSelectedFiltersState[t.name]),
  }))(tiers.indexOf(selectedTier));

Como eu preciso definir uma propriedade para indexOfSelectedTiere preciso usar esse valor ao definir a hasUpperTierSelectionpropriedade, eu calculo esse valor primeiro e o passo como parâmetro para o IIFE

Snekse
fonte
0

Outra abordagem seria declarar o objeto primeiro antes de atribuir propriedades a ele:

const foo = {};
foo.a = 5;
foo.b = 6;
foo.c = foo.a + foo.b;  // Does work
foo.getSum = () => foo.a + foo.b + foo.c;  // foo.getSum() === 22

Com isso, você pode usar o nome da variável do objeto para acessar os valores já atribuídos.
Melhor para config.jsarquivo.

5ervant
fonte
Isso não é uma referência própria, mas uma referência à variável declarada fooque aponta para o objeto em questão.
Derek Henderson
0
var x = {
    a: (window.secreta = 5),
    b: (window.secretb = 6),
    c: window.secreta + window.secretb
};

Isso é quase idêntico à resposta do @ slicedtoad, mas não usa uma função.

David Newberry
fonte
-1

Nota: Esta solução usa o Typecript (você pode usar o vanilla JS que o TS compila, se necessário)

class asd {
    def = new class {
        ads= 'asd';
        qwe= this.ads + '123';
    };

    // this method is just to check/test this solution 
    check(){
        console.log(this.def.qwe);
    }
}

// these two lines are just to check
let instance = new asd();
instance.check();

Aqui estavam usando expressões de classe para obter a interface literal do objeto aninhado que desejaríamos. Esta é a próxima melhor coisa que o IMHO pode fazer para referenciar as propriedades de um objeto durante a criação.

O principal a notar é que, ao usar esta solução, você tem exatamente a mesma interface que teria de um literal de objeto. E a sintaxe está bem próxima do literal de um objeto (vs usando uma função, etc).

Compare o seguinte

Solução que propus

class asd {
    def = new class {
        ads= 'asd';
        qwe= this.ads + '123';
    };

Solução se os literais do objeto fossem suficientes

var asd = {
    def : {
        ads:'asd',
        qwe: this.ads + '123';, //ILLEGAL CODE; just to show ideal scenario
    }
}

Outro exemplo

Aqui nesta classe, você pode combinar vários caminhos relativos entre si, o que não é possível com um objeto literal.

class CONSTANT {
    static readonly PATH = new class {
        /** private visibility because these relative paths don't make sense for direct access, they're only useful to path class
         *
         */
        private readonly RELATIVE = new class {
            readonly AFTER_EFFECTS_TEMPLATE_BINARY_VERSION: fs.PathLike = '\\assets\\aep-template\\src\\video-template.aep';
            readonly AFTER_EFFECTS_TEMPLATE_XML_VERSION: fs.PathLike = '\\assets\\aep-template\\intermediates\\video-template.aepx';
            readonly RELATIVE_PATH_TO_AFTER_EFFECTS: fs.PathLike = '\\Adobe\\Adobe After Effects CC 2018\\Support Files\\AfterFX.exe';
            readonly OUTPUT_DIRECTORY_NAME: fs.PathLike = '\\output';
            readonly INPUT_DIRECTORY_NAME: fs.PathLike = '\\input';
            readonly ASSETS_DIRECTORY_NAME: fs.PathLike = '\\assets';
        };
    }
}
Dheeraj Bhaskar
fonte
2
Poderia ser porque sua resposta é totalmente irrelevante? Concordo que downvoters deve explicar, mas a sua resposta clara não tem nada a ver com a questão ...
Manngo
@Manngo obrigado por apontar. Honestamente, eu teria a mesma pergunta que OP e uso a solução que sugeri. Incerto, por que está sendo considerado irrelevante. Se você tiver tempo, explique para que eu possa melhorar a resposta ou pelo menos saber onde estou errado. Infelizmente, não entendo por que essa não é uma solução razoável.
Dheeraj Bhaskar
-2

Se você deseja usar JS nativo, as outras respostas fornecem boas soluções.

Mas se você estiver disposto a escrever objetos de referência própria como:

{ 
  a: ...,
  b: "${this.a + this.a}",
}

Eu escrevi uma biblioteca npm chamada objeto auto-referenciado que suporta essa sintaxe e retorna um objeto nativo.

alex-e-leon
fonte
1
Por favor ligação evite só responde . As respostas que são "pouco mais que um link para um site externo" podem ser excluídas .
Quentin
@ Quentin, você tem alguma sugestão de como posso melhorar minha resposta? As outras respostas a esta pergunta abordam como você pode escrever objetos de auto-referência em javascript nativo, mas se quiser escrever objetos de auto-referência com uma sintaxe semelhante à sintaxe na pergunta original dos pôsteres, acho que a biblioteca que Escrevi pode ser útil para quem procura uma solução. Feliz por receber algum feedback.
Alex-e-leon
Algumas coisas para melhorar aqui. Primeiro, e mais obviamente, você está usando a sintaxe literal do modelo sem marcas de retorno. Seu bvalor de propriedade deve ser: ${this.a + this.a}. Segundo, mas menos importante, você gostaria de retornar um número, não uma string usando algo como parseInt. Por último e mais importante, quando tentei esse exemplo, ele simplesmente não funciona, pelo mesmo motivo que o OP está perguntando. Os thisretornos indefinidos quando usados ​​sua própria declaração de objeto. @ alex-e-leon
Alec Donald Mather
@AlecDonaldMather - obrigado por reservar um tempo para dar uma olhada e fornecer algum feedback! Se você estiver interessado no projeto, pode ser melhor mudar essa discussão para o github, mas responder alguns dos seus comentários: - Usando backticks: Como mencionado nos comentários anteriores, essa sintaxe não é suportada pelo JS e, portanto, usando strings de backticks é necessário aqui para evitar que js tente resolver "this" antes que o obj seja definido - retornando um número, isso deve funcionar se a + b já são números, já que a + b retornará um número se aeb forem já números.
alex-e-leon
Como esse retorno está indefinido, você pode explicar como tentou usar a biblioteca? Isso não deveria acontecer, mas talvez exista um caso que eu tenha deixado de fora? Dito isto, esta biblioteca não resolve o problema completamente e tem seu próprio conjunto de trocas, mas se você estiver interessado em me ajudar a melhorá-lo / usá-lo, avise-me!
alex-e-leon