web-dev-qa-db-fra.com

Auto-références dans les littéraux d'objet/les initialiseurs

Existe-t-il un moyen d'obtenir quelque chose comme le suivant pour travailler en JavaScript?

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

Dans le formulaire actuel, ce code génère évidemment une erreur de référence car this ne fait pas référence à foo. Mais is y a-t-il un moyen d'avoir des valeurs dans les propriétés d'un littéral d'objet dépendant d'autres propriétés déclarées précédemment?

563
kpozin

Eh bien, la seule chose que je peux vous dire, ce sont des accesseurs:

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

foo.c; // 11

Il s'agit d'une extension syntaxique introduite par la spécification ECMAScript 5th Edition. Cette syntaxe est prise en charge par la plupart des navigateurs modernes (notamment IE9).

561
CMS

Vous pourriez faire quelque chose comme:

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

Ce serait une sorte d'initialisation ponctuelle de l'objet.

Notez que vous assignez en réalité la valeur de retour de init() à foo; vous devez donc return this.

285
Felix Kling

Il manque la réponse évidente et simple, alors pour être complet:

Mais is y a-t-il un moyen d'avoir des valeurs dans les propriétés d'un littéral d'objet dépendant d'autres propriétés déclarées précédemment?

Non. Toutes les solutions présentées ici le reportent après la création de l'objet (de différentes manières), puis attribuent la troisième propriété. La la plus simple} méthode consiste simplement à faire ceci:

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

Tous les autres ne sont que des moyens plus indirects de faire la même chose. (Felix's est particulièrement intelligent, mais nécessite la création et la destruction d'une fonction temporaire, ce qui ajoute de la complexité; et laisse une propriété supplémentaire sur l'objet ou [si vous delete cette propriété] affecte la performance des accès de propriété ultérieurs sur cet objet. )

Si vous avez besoin que tout soit dans une même expression, vous pouvez le faire sans la propriété temporaire:

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

Ou bien sûr, si vous devez le faire plus d'une fois:

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

alors où vous devez l'utiliser:

var foo = buildFoo(5, 6);
150
T.J. Crowder

Instanciez simplement une fonction anonyme:

var foo = new function () {
    this.a = 5;
    this.b = 6;
    this.c = this.a + this.b;
};
59
zzzzBov

Une fermeture devrait régler ce problème;

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

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

Toutes les variables déclarées dans foo sont privées à foo, comme on peut s'y attendre avec n'importe quelle déclaration de fonction. Et comme elles sont toutes dans la portée, elles ont toutes accès les unes aux autres sans avoir besoin de se référer à this, comme on peut s'y attendre avec une fonction. La différence est que cette fonction renvoie un objet qui expose les variables privées et assigne cet objet à foo. En fin de compte, vous renvoyez uniquement l'interface que vous souhaitez exposer en tant qu'objet avec l'instruction return {}.

La fonction est ensuite exécutée à la fin avec le (), ce qui entraîne l'évaluation de l'objet foo entier, de toutes les variables dans l'instancié et de l'objet de retour ajouté en tant que propriétés de foo().

18
Henry Wrightson

Désormais, dans ES6, vous pouvez créer des propriétés en cache paresseuses. Lors de la première utilisation, la propriété est évaluée une fois pour devenir une propriété statique normale. Résultat: la deuxième fois que la surcharge de la fonction mathématique est ignorée. 

La magie est dans le getter.

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

Dans la flèche getter this prend le entourant la portée lexicale

foo     // {a: 5, b: 6}
foo.c   // 11
foo     // {a: 5, b: 6 , c: 11}  
17
voscausa

Tu pourrais le faire comme ça

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

Cette méthode s’est révélée utile pour moi lorsque je devais faire référence à l’objet sur lequel une fonction était initialement déclarée. Ce qui suit est un exemple minimal de la façon dont je l'ai utilisé:

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

En définissant self en tant qu'objet contenant la fonction d'impression, vous lui permettez de faire référence à cet objet. Cela signifie que vous n'aurez pas à lier la fonction d'impression à un objet si vous devez le transmettre ailleurs. 

Si vous préférez utiliser this comme illustré ci-dessous

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

Ensuite, le code suivant enregistre 0, 1, 2 puis donne une erreur

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!

En utilisant la méthode automatique, vous garantissez que print retournera toujours le même objet, quel que soit le contexte dans lequel la fonction est exécutée. Le code ci-dessus fonctionnera parfaitement et consignera les valeurs 0, 1, 2 et 3 lors de l'utilisation de la version autonome de createMyObject()

14
davedambonite

Il y a plusieurs façons d'accomplir cela. Voici ce que je voudrais utiliser:

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
7
ken

Pour terminer, dans ES6, nous avons des classes (prises en charge au moment de l'écriture, uniquement par les navigateurs les plus récents, mais disponibles dans Babel, TypeScript et d'autres transpilers).

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

const foo = new Foo();
7
monzonj

pour des raisons de pensée, placez les propriétés d'un objet hors d'un scénario:

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

console.log(foo.c())

il y a de meilleures réponses ci-dessus aussi. Voici comment j'ai modifié le code d'exemple que vous avez interrogé.

METTRE À JOUR:

var foo = {
    get a(){return 5},
    get b(){return 6},
    get c(){return this.a + this.b}
}
// console.log(foo.c);
7
animaacija

Vous pouvez le faire en utilisant le modèle de module. Juste comme:

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

Avec ce modèle, vous pouvez instancier plusieurs objets foo en fonction de vos besoins.

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

7
Rafael Rocha

Créer une nouvelle fonction sur le littéral de votre objet et invoquer un constructeur semble constituer un changement radical par rapport au problème initial, et est inutile. 

Vous ne pouvez pas référencer une propriété frère lors de l'initialisation du littéral d'objet. 

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

La solution la plus simple pour les propriétés calculées est la suivante (pas de tas, pas de fonctions, pas de constructeur): 

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

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

La clé de tout cela est PORTÉE.

Vous devez encapsuler le "parent" (objet parent) de la propriété que vous souhaitez définir comme étant son propre objet instancié. Vous pouvez ensuite faire référence aux propriétés du même parent à l'aide de la clé Word this

Il est très, très important de se rappeler que si vous faites référence à this sans le faire d'abord, alors this fera référence à l'étendue externe ... qui sera l'objet window.

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)
};
3
user3606367

Les autres réponses postées ici sont meilleures mais voici une alternative:

  • Définit la valeur à l'initialisation (pas un getter, ni dérivé, etc.)
  • Ne nécessite aucun type de init() ou de code en dehors du littéral d'objet
  • Est un objet littéral et non une fonction d'usine ou un autre mécanisme de création d'objet.
  • Ne devrait pas avoir d'impact sur les performances (sauf lors de l'initialisation) 

Fonctions anonymes auto-exécutables et stockage de fenêtres

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

La commande est garantie (bar avant baz).

Bien sûr, cela pollue window, mais je ne peux pas imaginer que quelqu'un écrive un script nécessitant que window.temp soit persistant. Peut-être tempMyApp si vous êtes paranoïaque.

C'est aussi moche mais parfois utile. Par exemple, lorsque vous utilisez une API avec des conditions d’initialisation rigides et que vous n’avez pas l’impression de la refactoriser, la portée est correcte.

Et c'est sec, bien sûr.

2
slicedtoad

si votre objet est écrit sous forme de fonction qui retourne un objet ET que vous utilisez les méthodes d'attribut d'objet ES6, alors il est possible:

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);
2
daviestar

Voici une façon soignée ES6:

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

Je l'utilise pour faire quelque chose comme ça:

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]);

2
UDrake

Que diriez-vous de cette solution cela fonctionnera avec des objets imbriqués avec tableau aussi bien

      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');
1
Gaurav

J'utilise le code suivant comme alternative, et cela fonctionne. Et la variable peut aussi être un tableau. (@ 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]
1
Indiana

Une approche différente, pour simplifier un peu les choses et ne pas définir de nouvelles fonctions dans l'objet, consiste à conserver l'objet d'origine avec ses propriétés, créer un deuxième objet avec les propriétés dynamiques qui utilisent les propriétés du premier puis fusionnez en utilisant spread .

Alors:

Objet

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

Object 2

var foo2 = {
    c: foo.a + foo.b
};

Fusionner

Object.assign(foo, foo2);

ou

foo = {...foo, ...foo2};

Résultat de foo: _

{ 
  "a":5,
  "b":6,
  "c":11
}

Tous dans le même js, pas de nouvelles fonctions dans l'objet, seulement en utilisant assign ou spread.

0
c-chavez

Une autre approche consisterait à déclarer l’objet d’abord avant de lui affecter des propriétés:

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

Avec cela, vous pouvez utiliser le nom de la variable d'objet pour accéder aux valeurs déjà attribuées.
Idéal pour le fichier config.js.

0
5ervant

Jeter une option puisque je n'ai pas vu ce scénario exact couvert. Si vous ne voulez pas souhaitez que c soit mis à jour lorsque a ou b est mis à jour, un ES6 IIFE fonctionne bien. 

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

Pour mes besoins, j'ai un objet qui se rapporte à un tableau qui finira par être utilisé dans une boucle, je ne souhaite donc calculer qu'une seule configuration commune, c'est donc ce que j'ai:

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

Étant donné que je dois définir une propriété pour indexOfSelectedTier et que je dois utiliser cette valeur lors de la définition de la propriété hasUpperTierSelection, je calcule cette valeur d'abord et la passe en tant que paramètre à l'IIFE.

0
Snekse