Autorreferencias en literales / inicializadores de objeto

705

¿Hay alguna manera de hacer que algo como lo siguiente funcione en JavaScript?

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

En la forma actual, este código obviamente arroja un error de referencia, ya thisque no hace referencia foo. Pero, ¿ hay alguna forma de tener valores en las propiedades de un objeto literal que dependen de otras propiedades declaradas anteriormente?

kpozin
fuente

Respuestas:

744

Bueno, lo único que te puedo decir es getter :

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

console.log(foo.c) // 11

Esta es una extensión sintáctica introducida por la especificación ECMAScript 5th Edition, la sintaxis es compatible con la mayoría de los navegadores modernos (incluido IE9).

CMS
fuente
32
Muy útil respuesta. Puede encontrar más información sobre 'get' aquí: developer.mozilla.org/en-US/docs/JavaScript/Reference/Operators/…
jake
55
@FaustoR. Si lo hace
Charlie Martin
48
Tenga en cuenta que con esta solución, si los valores de foo.ao foo.bse cambian, el valor de foo.ctambién cambiará en sincronismo. Esto puede o no ser lo que se requiere.
HBP
8
Tenga en cuenta que se thisune al objeto anidado más profundo. Por ejemplo:... x: { get c () { /*this is x, not foo*/ } } ...
Z. Khullah
3
Para completar mi declaración anterior, dado que foose declara como una variable y csolo se evaluará en el momento en que se invoca, el uso foointerno cfuncionará, en lugar de this(tenga cuidado)
Z. Khullah
316

Podrías hacer algo como:

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

Esto sería una especie de inicialización única del objeto.

Tenga en cuenta que en realidad está asignando el valor de retorno de init()a foo, por lo tanto, debe hacerlo return this.

Felix Kling
fuente
95
también puedes hacerlo delete this.initantes return thispara que foono se contamine
Billy Moon
15
@BillyMoon: Sí, aunque hacerlo afecta el rendimiento de todos los accesos de propiedades posteriores en ese objeto, en muchos motores (V8, por ejemplo).
TJ Crowder
8
@MuhammadUmer: No estoy seguro de cómo las clases de ES6 son relevantes para la pregunta.
Felix Kling
8
@MuhammadUmer: las clases son simplemente azúcar sintáctica para las funciones de constructor, por lo que realmente no proporcionan nada nuevo. De cualquier manera, el foco principal de esta pregunta son los literales de objeto.
Felix Kling
3
@akantoword: Genial :) ya que los literales de objeto son una sola expresión, la init()llamada se agregó directamente al literal para mantener una sola expresión. Pero, por supuesto, puede llamar a la función por separado de lo que desea.
Felix Kling
181

Falta la respuesta simple y obvia, así que para completar:

Pero, ¿ hay alguna forma de que los valores en las propiedades de un objeto literal dependan de otras propiedades declaradas anteriormente?

No. Todas las soluciones aquí difieren hasta que se crea el objeto (de varias maneras) y luego asignan la tercera propiedad. La forma más simple es simplemente hacer esto:

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

Todos los demás son formas más indirectas de hacer lo mismo. (Felix's es particularmente inteligente, pero requiere crear y destruir una función temporal, agregar complejidad; y deja una propiedad adicional en el objeto o [si tiene deleteesa propiedad] afecta el rendimiento de los accesos de propiedad posteriores en ese objeto).

Si necesita que todo esté dentro de una expresión, puede hacerlo sin la propiedad temporal:

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

O, por supuesto, si necesita hacer esto más de una vez:

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

entonces donde necesitas usarlo:

var foo = buildFoo(5, 6);
TJ Crowder
fuente
Para mi propia cordura, estoy tratando de encontrar algún tipo de documentación oficial que diga básicamente lo mismo: que un objeto thissolo está disponible para los métodos de dicho objeto, y no hay otro tipo de propiedades. ¿Alguna idea de dónde podría encontrar eso? ¡Gracias!
David Kennell
1
@DavidKennell: no se vuelve más oficial que la especificación . :-) Probablemente comenzarías aquí y lo seguirías. Es un lenguaje bastante extraño, pero básicamente verá en las diversas subcláusulas de la Evaluación de definición de propiedades que el objeto no está disponible para las operaciones que determinan los valores de los inicializadores de propiedades.
TJ Crowder
No puedo ver los resultados del buscador aquí , pero este ya no es el caso, ¿verdad? En mi entorno, v8: deletees un 10% más rápido y gecko: deletees solo un 1% más lento.
TheMaster
1
@TheMaster - Sí, ya no creo que BrowserScope sea realmente una cosa. Parece que eliminar no es tan malo como solía ser, al menos no en V8 (Chrome, etc.) o SpiderMonkey. Todavía más lento, pero solo un poquito, y estas cosas son muy rápidas estos días.
TJ Crowder
63

Simplemente instancia una función anónima:

var foo = new function () {
    this.a = 5;
    this.b = 6;
    this.c = this.a + this.b;
};
zzzzBov
fuente
1
@ Bergi, ¿por qué? ¿Porque alguien podría crear una instancia de otro objeto del mismo? No es que no puedan simplemente clonar un objeto literal. No es diferente a pasar un argumento como, new Point(x, y)excepto que la función no tiene nombre para su reutilización.
zzzzBov
1
@zzzzBov: Por supuesto, solo pueden clonar el objeto, pero en comparación con una solución IEFE (como en la respuesta de TJCrowder), su solución pierde la función del constructor y crea un prototipo superfluo.
Bergi
55
@zzzzBov: solo usa el var foo = function() { this.…; return this; }.call({});que es sintácticamente no muy diferente pero semánticamente sano.
Bergi
1
@ Bergi, si cree que es tan importante, ¿por qué no agrega su propia respuesta a la mezcla?
zzzzBov
3
Tienes esto. De hecho, no noté la newpalabra clave.
Randy
26

Ahora en ES6 puede crear propiedades en caché perezosas. En el primer uso, la propiedad se evalúa una vez para convertirse en una propiedad estática normal. Resultado: la segunda vez que se omite la sobrecarga de la función matemática.

La magia está en el captador.

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

En la flecha, el captador thisrecoge el alcance léxico circundante .

foo     // {a: 5, b: 6}
foo.c   // 11
foo     // {a: 5, b: 6 , c: 11}  
voscausa
fuente
1
es5 también tiene propiedades que solo necesita usar Object.defineProperty(foo, 'c', {get:function() {...}})para definirlas. Esto se hace fácilmente de una manera discreta en una fábrica como esta. Por supuesto, si puede usar el getazúcar, es más legible, pero la capacidad ha estado allí.
Aluan Haddad
21

Algún cierre debería ocuparse de esto;

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

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

Todas las variables declaradas fooson privadas foo, como cabría esperar con cualquier declaración de función y, dado que están todas dentro del alcance, todas tienen acceso entre sí sin necesidad de hacer referencia this, tal como cabría esperar con una función. La diferencia es que esta función devuelve un objeto que expone las variables privadas y asigna ese objeto foo. Al final, devuelve solo la interfaz que desea exponer como un objeto con la return {}instrucción.

La función se ejecuta al final con lo ()que hace que se evalúe todo el objeto foo, todas las variables dentro de las instancias y el objeto de retorno agregado como propiedades de foo().

Henry Wrightson
fuente
14
Es confuso y engañoso llamar a esto un "cierre". Aunque las opiniones difieren sobre el significado preciso que devuelve un valor ojbect de una función, no constituye un cierre en el libro de nadie.
16

Podrías hacerlo así

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

Ese método me ha resultado útil cuando tuve que referirme al objeto en el que se declaró originalmente una función. El siguiente es un ejemplo mínimo de cómo lo usé:

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

Al definir self como el objeto que contiene la función de impresión, permite que la función haga referencia a ese objeto. Esto significa que no tendrá que 'vincular' la función de impresión a un objeto si necesita pasarla a otro lugar.

Si, en cambio, lo usa thiscomo se ilustra a continuación

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

Luego, el siguiente código registrará 0, 1, 2 y luego dará un error

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!

Al utilizar el método self, garantiza que print siempre devolverá el mismo objeto, independientemente del contexto en el que se ejecute la función. El código anterior se ejecutará bien y registrará 0, 1, 2 y 3 cuando use la versión propia de createMyObject().

davedambonita
fuente
3
Buenos ejemplos, excepto que has omitido todos los puntos y comas.
1
Sin punto y coma - ¡ Está bien !
Kerem Baydogan el
9

Para completar, en ES6 tenemos clases (admitidas en el momento de escribir esto solo por los navegadores más recientes, pero disponibles en Babel, TypeScript y otros transpiladores)

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

const foo = new Foo();
monzonj
fuente
7

Puede hacerlo utilizando el patrón del módulo. Al igual que:

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

Con este patrón, puede crear instancias de varios objetos foo según sus necesidades.

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

Rafael Rocha
fuente
2
Este no es un ejemplo del patrón del módulo, solo una función. Si la última línea de la definición foo fuera }();, se ejecutaría automáticamente y devolvería un objeto, no una función. Además, foo.ces una función, por lo que escribir en él activa esa función y fallará la próxima invocación a través de fooObject.c(). Tal vez este violín esté más cerca de lo que está buscando (también es un singleton, no diseñado para ser instanciado).
Hollister
2
"El patrón Módulo se definió originalmente como una forma de proporcionar encapsulación tanto privada como pública para las clases de ingeniería de software convencional". De: Aprendizaje de patrones de diseño de JavaScript . Ese objeto sigue el patrón del módulo descrito anteriormente, pero tal vez no sea el mejor para explicarlo porque no muestra propiedades / métodos públicos y privados. Este jsfiddle.net/9nnR5/2 es el mismo objeto con propiedades / métodos públicos y privados. Así que ambos siguen este patrón
Rafael Rocha
6

Hay varias maneras de lograr esto; esto es lo que usaría:

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
conocer
fuente
2
Esto funciona, pero elimina las ventajas de la notación literal del objeto.
kpozin
Es cierto, lo siento, me perdí la etiqueta de objeto literal originalmente. Principalmente solo uso literales de objeto para estructuras de datos, y cada vez que quiero una lógica adicional (que podría parecerse a una clase) creo el objeto como resultado de una función por esta misma razón.
Ken
6

solo por el pensamiento: coloque las propiedades del objeto fuera de una línea de tiempo:

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

console.log(foo.c())

Hay mejores respuestas arriba también . Así es como modifiqué el código de ejemplo con el que cuestionaste.

ACTUALIZAR:

var foo = {
    get a(){return 5},
    get b(){return 6},
    get c(){return this.a + this.b}
}
// console.log(foo.c);
animaacija
fuente
2
En ES6 puede hacer que este enfoque general mucho más elegante: var foo = { get a(){return 5}, get b(){return 6}, get c(){return this.a + this.b} }por lo que ahora sólo se puede hacer foo.cen lugar de foo.c():) (No dude en pegar esto en su respuesta de modo de formatear es mejor!)
5

Crear una nueva función en su objeto literal e invocar a un constructor parece una desviación radical del problema original, y es innecesario.

No puede hacer referencia a una propiedad hermana durante la inicialización literal del 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 

La solución más simple para propiedades calculadas sigue (sin montón, sin funciones, sin constructor):

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

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

Las otras respuestas publicadas aquí son mejores, pero aquí hay una alternativa que:

  • Establece el valor en la inicialización (no un captador, derivado, etc.)
  • No requiere ningún tipo init()o código fuera del objeto literal
  • Es un objeto literal y no una función de fábrica u otra mecánica de creación de objetos.
  • No debería tener ningún impacto en el rendimiento (excepto en la inicialización)

Funciones anónimas de ejecución automática y almacenamiento de ventanas

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

El pedido está garantizado ( barantes baz).

Contamina, windowpor supuesto, pero no puedo imaginar a alguien escribiendo un guión que requiera window.tempser persistente. Quizás tempMyAppsi eres paranoico.

También es feo pero ocasionalmente útil. Un ejemplo es cuando está utilizando una API con condiciones de inicialización rígidas y no tiene ganas de refactorizar, por lo que el alcance es correcto.

Y está seco, por supuesto.

DanielST
fuente
3

La clave de todo esto es ALCANCE .

Debe encapsular el "padre" (objeto padre) de la propiedad que desea definir como su propio objeto instanciado, y luego puede hacer referencias a las propiedades de los hermanos utilizando la palabra clave this

Es muy, muy importante recordar que si te refieres thissin hacerlo primero, thisse referirá al alcance externo ... que será el 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)
};
Comunidad
fuente
2
Esto ni siquiera es válido JS.
2

si su objeto está escrito como una función que devuelve un objeto, Y usted usa 'métodos' de atributos de objeto ES6, entonces es posible:

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
fuente
2

Yo uso el siguiente código como alternativa, y funciona. Y la variable también puede 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
fuente
2

Aquí hay una forma ordenada de ES6:

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

Lo uso para hacer algo como esto:

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
fuente
1

¿Qué tal esta solución? Esto también funcionará con objetos anidados con 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
fuente
0

Lanzar una opción ya que no vi este escenario exacto cubierto. Si no desea cactualizar cuándo ao bactualizar, entonces un ES6 IIFE funciona bien.

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

Para mis necesidades, tengo un objeto que se relaciona con una matriz que terminará siendo utilizada en un bucle, por lo que solo quiero calcular una configuración común una vez, así que esto es lo que tengo:

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

Como necesito establecer una propiedad para indexOfSelectedTiery necesito usar ese valor al establecer la hasUpperTierSelectionpropiedad, calculo primero ese valor y lo paso como parámetro al IIFE

Snekse
fuente
0

Otro enfoque sería declarar el objeto primero antes de asignarle propiedades:

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

Con eso, puede usar el nombre de la variable del objeto para acceder a los valores ya asignados.
Lo mejor para el config.jsarchivo.

5viente
fuente
Esa no es una referencia propia, sino más bien una referencia a la variable declarada fooque apunta al objeto en cuestión.
Derek Henderson el
0
var x = {
    a: (window.secreta = 5),
    b: (window.secretb = 6),
    c: window.secreta + window.secretb
};

Esto es casi idéntico a la respuesta de @ slicedtoad, pero no utiliza una función.

David Newberry
fuente
-1

Nota: Esta solución usa el Script mecanografiado (puede usar el JS de Vanilla que TS compila si es necesario)

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

Aquí usamos expresiones de clase para obtener la interfaz literal del objeto anidado que desearíamos. Esto es lo mejor en mi humilde opinión para poder hacer referencia a las propiedades de un objeto durante la creación.

Lo principal a tener en cuenta es que al usar esta solución, tiene exactamente la misma interfaz que habría tenido de un objeto literal. Y la sintaxis está bastante cerca de un objeto literal en sí mismo (en comparación con el uso de una función, etc.).

Compara lo siguiente

Solución que he propuesto

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

Solución si los literales de objetos hubieran sido suficientes

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

Otro ejemplo

Aquí en esta clase, puede combinar múltiples rutas relativas entre sí, lo que no es posible con un 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
fuente
2
¿Podría ser porque su respuesta es totalmente irrelevante? Estoy de acuerdo en que los downvoters deberían explicarlo, pero su respuesta clara no tiene nada que ver con la pregunta ...
Manngo
@Manngo, gracias por señalarlo. Honestamente, tengo la misma pregunta que OP y uso la solución que he sugerido. No estoy seguro de por qué se considera irrelevante. Si tiene tiempo, explíquelo para que pueda mejorar la respuesta o al menos saber dónde me equivoco. Lamentablemente, no entiendo por qué esta no es una solución razonable.
Dheeraj Bhaskar
-2

Si desea utilizar JS nativo, las otras respuestas proporcionan buenas soluciones.

Pero si está dispuesto a escribir objetos de autorreferencia como:

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

Escribí una biblioteca npm llamada self-referenced-object que admite esa sintaxis y devuelve un objeto nativo.

alex-e-leon
fuente
1
Por favor, evite las respuestas de enlace solamente . Respuestas que son "poco más que un enlace a un sitio externo” se pueden eliminar .
Quentin
@Quentin, ¿tiene alguna sugerencia sobre cómo puedo mejorar mi respuesta? Las otras respuestas a esta pregunta cubren cómo podría escribir objetos de autorreferencia en JavaScript nativo, pero si desea escribir objetos de autorreferencia con una sintaxis similar a la sintaxis en la pregunta original de los carteles, creo que la biblioteca que Escribí puede ser útil para otros que buscan una solución. Feliz de recibir algunos comentarios.
alex-e-leon
Un par de cosas para mejorar aquí. Primero, y lo más obvio, está usando la sintaxis literal de la plantilla sin ticks posteriores. Su bvalor de la propiedad debe ser: ${this.a + this.a}. En segundo lugar, pero menos importante, querrás devolver un número, no una cadena, usando algo como parseInt. Por último y MÁS importante, cuando probé este ejemplo, simplemente no funciona, por la misma razón que el OP pregunta. Los thisretornos no están definidos cuando se usa su propia declaración de objeto. @ alex-e-leon
Alec Donald Mather
@AlecDonaldMather: ¡gracias por tomarse el tiempo de echar un vistazo y brindar sus comentarios! Si está interesado en el proyecto, podría ser mejor pasar esta discusión a github, pero responder algunos de sus comentarios: - Uso de backticks: como se mencionó en los comentarios anteriores, JS no admite esta sintaxis y, por lo tanto, usa cadenas de backticks se requiere aquí para evitar que js intente resolver "esto" antes de que se haya definido el obj: devolver un número, esto debería funcionar si a + b ya son números, ya que a + b devolverá un número si tanto a como b son Ya números.
alex-e-leon
Con respecto a este retorno indefinido, ¿puede explicar cómo trató de usar la biblioteca? Esto no debería suceder, pero ¿tal vez hay un caso marginal que he dejado fuera? Dicho esto, esta biblioteca no resuelve el problema por completo y tiene su propio conjunto de compensaciones, pero si está interesado en ayudarme a mejorarlo / usarlo, ¡hágamelo saber!
alex-e-leon