Self-references in object literals / initializers
Asked Answered
S

32

925

Is there any way to get something like the following to work in JavaScript?

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

In the current form, this code obviously throws a reference error since this doesn't refer to foo. But is there any way to have values in an object literal's properties depend on other properties declared earlier?

Spinescent answered 6/1, 2011 at 14:53 Comment(0)
K
1028

Well, the only thing that I can tell you about are getter:

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

console.log(foo.c) // 11

This is a syntactic extension introduced by the ECMAScript 5th Edition Specification, the syntax is supported by most modern browsers (including IE9).

Kironde answered 6/1, 2011 at 14:58 Comment(13)
Very helpful answer. More info on 'get' can be found here: developer.mozilla.org/en-US/docs/JavaScript/Reference/Operators/…Wartburg
Beware that with this solution if the values of foo.a or foo.b are changed then the value of foo.c will also change in synchronism. This may or may not be what is required.Jens
@Jens That would be the exact same thing as what would happen in the question, so it seems to me like that is exactly what the expected result had to be.Vigor
Does the "function" c actually gets executed each time foo.c is dereferenced?Vladamar
This looks functionally equivalent to: ... c: function() { return this.a + this.b; } ... foo.c(), any gotchas with this assumption?Archery
FYI getter functions do not allow parameters I believe -- does not apply to this case but I think that's one difference to be noted.Vaughn
do you think it is possible to "imitate" getter features in es3? maybe a polify?Serrato
Also wanted to mention that if you assign the getter prop to another object, the result is evaluated, so you're not moving a function, where the this binding can be lost, but also means you're not moving the "getter", you're evaluating the getter and then copying the result.Besse
Note that this binds to the deepest nested object. E.g.: ... x: { get c () { /*this is x, not foo*/ } } ...Silvern
To complete my above statement, since foo is beeing declared as a variable and c will only be evaluated at the time it is invoked, using foo inside c will work, as opposed to this (be careful though)Silvern
If one wanted further optimization, one could also assign the c property in the getter, so that it's only calculated once, rather than on every access of .c.Novel
@Novel Yes, but you'll need to delete the getter first (within the getter) as in this answer. Key to note, if a property has a getter, you can only change its value after deleting the getter or using Object.defineProperty. If you're using a self-deleting (i.e. memoized) getter, later reassignment will fail if the property hasn't been accessed at least once.Bousquet
also see the binded closure example belowGaul
B
354

You could do something like:

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

This would be some kind of one time initialization of the object.

Note that you are actually assigning the return value of init() to foo, therefore you have to return this.

Beech answered 6/1, 2011 at 14:59 Comment(15)
you can also delete this.init before return this so that foo is not polutedPerrone
@BillyMoon: Yes indeed, although doing so impacts performance of all subsequent property accesses on that object, on many engines (V8, for instance).Draper
this in ES6 classes works as expected. So no need to call init there.Peter
@MuhammadUmer: Not sure how ES6 classes are relevant to the question.Beech
@MuhammadUmer: classes are just syntactic sugar for constructor functions, so they don't really provide anything new. Either way, the main focus of this question are object literals.Beech
why do you have to return this at the end?Letterpress
@akantoword: I'm assigning the return value of init to foo, so I have to return the object itself.Beech
@FelixKling but why do you have to return something though, even if you don't have that return statement, shouldn't the init function do it's job of creating 'c'? why does it have to have the return statement to work?Letterpress
@akantoword: Remove the return statements and try it. Of course c is created, but because init doesn't return anything, foo will be undefined, so there is no way to use / access the object afterwards. Again: The return value of init is assigned to foo.Beech
@FelixKling why is the return value of init assigned to foo? sorry if this is off topic, but it doesn't make sense to me that init has to return something because it's purpose is to create c. to me, init is just another property of foo, so when foo is defined with the object literal, it shouldn't be dependent on init to return itself. I'm pretty sure I'm missing something very basic here, but I don't know what.Letterpress
@akantoword: In the last line, init is called immediately. If we omit what's inside the object literal, it would look like var foo = { ... }.init();. Is it clearer now why the return value of init is assigned to foo? We could also do this in multiple statements, then init wouldn't have to return anything: var foo = { ... }; foo.init();.Beech
@FelixKling that made it much more clear, thank you! I didn't know it'd make a difference with 2 statements vs 1.Letterpress
@akantoword: Great :) since object literals are a single expression, the init() call was directly appended the literal to keep it a single expression. But of course you can call the function separately of you want to.Beech
@tambre: Interesting. But of course, that's just V8, not other engines.Draper
If others are still confused, its helpful to highlight the dot part of .init(), you are chaining this on to the definition. Actually a good way to see this is that when you run this, you get back whatever the last function on the line returns. See this perhaps as chained functions in jQuery, so you are chaining the definition of the object with the running of the init function. if the init function did not return anything, as mentioned previously you'd just get undefined.Babita
D
229

The obvious, simple answer is missing, so for completeness:

But is there any way to have values in an object literal's properties depend on other properties declared earlier?

No. All of the solutions here defer it until after the object is created (in various ways) and then assign the third property. The simplest way is to just do this:

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

All others are just more indirect ways to do the same thing. (Felix's is particularly clever, but requires creating and destroying a temporary function, adding complexity; and either leaves an extra property on the object or [if you delete that property] impacts the performance of subsequent property accesses on that object.)

If you need it to all be within one expression, you can do that without the temporary property:

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

Or of course, if you need to do this more than once:

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

then where you need to use it:

var foo = buildFoo(5, 6);
Draper answered 26/5, 2012 at 12:11 Comment(4)
For my own sanity, I'm trying to find some kinda of official documentation that says basically the same thing - that an object's this is only available to methods of said object, and no other kinds of properties. Any idea where I could find that? Thanks!Statesmanship
@DavidKennell: Doesn't get more official than the specification. :-) You'd probably start here and follow it through. It's fairly awkard language, but basically you'll see in the various subclauses of Property Definition Evaluation that the object isn't available to the operations determining the values of property initializers.Draper
I can't see the browserscope results here, but this is no more the case right? In my environment, v8: delete is 10% faster and gecko : delete is just 1% slower.Teetotal
@Teetotal - Yeah, I don't think BrowserScope is really a thing anymore. Looks like deleting isn't as bad as it used to be, at least not in V8 (Chrome, etc.) or SpiderMonkey. Still slower, but only a tiny bit, and these things are freaky fast these days.Draper
P
72

Simply instantiate an anonymous function:

var foo = new function () {
    this.a = 5;
    this.b = 6;
    this.c = this.a + this.b;
};
Pissed answered 11/4, 2013 at 22:32 Comment(7)
@Bergi, why? Because someone might instantiate another of the same object from it? It's not like they can't just clone an object literal. It's no different than passing an argument like new Point(x, y) except that the function isn't named for reuse.Pissed
@zzzzBov: Of course they can just clone the object, but compared to an IEFE solution (like in TJCrowder's answer) your solution leaks the constructor function and creates a superfluous prototype object.Lugsail
@Bergi, sure, but this is significantly more expressive.Pissed
@zzzzBov: Just use var foo = function() { this.…; return this; }.call({}); which is syntactically not much different but semantically sane.Lugsail
@Bergi, if you feel it's that important, why not add your own answer to the mix?Pissed
@Pissed I think it's just a variation of TJ's answerLugsail
You've got this. I indeed did not notice the new keyword.Vigor
C
42

Now in ES6 you can create lazy cached properties. On first use the property evaluates once to become a normal static property. Result: The second time the math function overhead is skipped.

The magic is in the getter.

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

In the arrow getter this picks up the surrounding lexical scope.

foo     // {a: 5, b: 6}
foo.c   // 11
foo     // {a: 5, b: 6 , c: 11}  
Cabral answered 8/11, 2016 at 23:43 Comment(5)
es5 also has properties you just need to use Object.defineProperty(foo, 'c', {get:function() {...}}) to define them. This is easily done in an unobtrusive way in a factory such as this one. Of course if you can use the get sugar it is more readable but the capability has been there.Tupiguarani
this works perfectly, but may I know why are you deleting this.c when it doesn't even exists ? I tried without writing delete this.c but it didn't workBushire
I was scratching my head about the delete as well. I think what it's doing is removing the get c property from the object and overwriting it with a standard property. I think this way it will only compute once and then foo.c will not update its values if a or b change later, but this also only works/caches/computes upon calling foo.c.Halinahalite
Yes this is what is called lazy evaluation. More here: en.wikipedia.org/wiki/Lazy_evaluationCabral
The point of delete this.c is to remove the getter and then proceed to replace it with a single numeric value. Doing it this way avoids needing to execute the getter every time foo.c is used. The return statement both creates the replacement property foo.c and returns its value. It would be possible to use get c() { return this.a + this + b } but that would be re-evaluated every time foo.c is used.Auer
E
27

Some closure should deal with this;

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

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

All the variables declared within foo are private to foo, as you would expect with any function declaration and because they are all in scope, they all have access to each other without needing to refer to this, just as you would expect with a function. The difference is that this function returns an object that exposes the private variables and assigns that object to foo. In the end, you return just the interface you want to expose as an object with the return {} statement.

The function is then executed at the end with the () which causes the entire foo object to be evaluated, all the variables within instantiated and the return object added as properties of foo().

Elery answered 2/7, 2013 at 9:4 Comment(1)
It is confusing and misleading to call this a "closure". Although opinions differ on the precise meaning returning an ojbect value from a function does not constitute a closure in anyone's book.Presentational
D
18

You could do it like this

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

That method has proven useful to me when I had to refer to the object that a function was originally declared on. The following is a minimal example of how I used it:

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

By defining self as the object that contains the print function you allow the function to refer to that object. This means you will not have to 'bind' the print function to an object if you need to pass it somewhere else.

If you would, instead, use this as illustrated below

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

Then the following code will log 0, 1, 2 and then give an 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!

By using the self method you guarantee that print will always return the same object regardless of the context in which the function is ran. The code above will run just fine and log 0, 1, 2 and 3 when using the self version of createMyObject().

Dehumanize answered 24/5, 2013 at 22:25 Comment(0)
O
12

For completion, in ES6 we've got classes (supported at the time of writing this only by latest browsers, but available in Babel, TypeScript and other transpilers)

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

const foo = new Foo();
Outface answered 28/3, 2016 at 12:41 Comment(0)
B
8

just for the sake of thought - place object's properties out of a timeline:

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

console.log(foo.c())

there are better answers above too. This is how I modified example code you questioned with.

UPDATE:

var foo = {
    get a(){return 5},
    get b(){return 6},
    get c(){return this.a + this.b}
}
// console.log(foo.c);
Benne answered 11/8, 2015 at 0:0 Comment(2)
In ES6 you can make this general approach much more elegant: var foo = { get a(){return 5}, get b(){return 6}, get c(){return this.a + this.b} } so now you can just do foo.c instead of foo.c() :) (Feel free to paste that into your answer so formatting is better!)Mcandrew
Note that this recomputes foo.c every time it's used. This may or may not be what you're looking for.Auer
D
7

You can do it using the module pattern. Just like:

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

With this pattern you can instantiate several foo objects according to your need.

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

Domesticate answered 8/1, 2013 at 0:21 Comment(2)
This isn't an example of the module pattern, just a function. If the last line of the foo definition was }();, it would self execute and return an object, not a function. Also, foo.c is a function, so writing to it clobbers that function and the next invocation via fooObject.c() will fail. Maybe this fiddle is closer to what you're going for (it's also a singleton, not designed to be instantiated).Sapers
"The Module pattern was originally defined as a way to provide both private and public encapsulation for classes in conventional software engineering". From: Learning JavaScript Design Patterns. That's object follow the module pattern described above but maybe it isn't the best one to explain that because is not showing public and private properties/methods. This one jsfiddle.net/9nnR5/2 is the same object with public and private properties/methods. So both of them are following this patternDomesticate
H
6

There are several ways to accomplish this; this is what I would use:

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
Hazlitt answered 6/1, 2011 at 18:55 Comment(2)
This works, but eliminates the advantages of the object literal notation.Spinescent
True, sorry I missed the object-literal tag originally. I mostly only use object literals for data structures, and anytime I want any additional logic (which might resemble a class) I create the object as the result of a function for this very reason.Hazlitt
G
6

The get property works great, and you can also use a binded closure for "expensive" functions that should only run once (this only works with var, not with const or let)

var info = {
  address: (function() {
    return databaseLookup(this.id)
  }).bind(info)(),

  get fullName() {
    console.log('computing fullName...')
    return `${this.first} ${this.last}`
  },

  id: '555-22-9999',
  first: 'First',
  last: 'Last',
}

function databaseLookup() {
  console.log('fetching address from remote server (runs once)...')
  return Promise.resolve(`22 Main St, City, Country`)
}

// test
(async () => {
  console.log(info.fullName)
  console.log(info.fullName)
  console.log(await info.address)
  console.log(await info.address)
  console.log(await info.address)
  console.log(await info.address)
})()
Gaul answered 1/11, 2020 at 3:25 Comment(0)
F
6

Just for everyone's amusement:

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

console.log(foo);
Fiduciary answered 25/1, 2022 at 23:45 Comment(4)
haha :-)...........Pragmatist
Interesting hack. You would need to add var or let in front of first This to make it work in strict mode.Auer
@MikkoRantalainen No, This is a parameter (this isn't exactly obvious, I know). It doesn't need a preceding variable declaration keyword.Lubalubba
Oh, I originally read that code as using object initializer for defining This and as far as I know, those always require declaration in strict mode. However, as you correctly point out, that's actually function definition with arrow syntax and This is the function parameter that simply "happens" to have default value that matches an implicit object. I consider this as an yet another example where arrow functions reduce readability of JS code a lot because you have to do lots of backtracking to correctly parse the code.Auer
R
5

Creating new function on your object literal and invoking a constructor seems a radical departure from the original problem, and it's unnecessary.

You cannot reference a sibling property during object literal initialization.

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

The simplest solution for computed properties follows (no heap, no functions, no constructor):

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

x.c = x.a + x.b; // apply computed property
Raucous answered 13/9, 2014 at 21:36 Comment(0)
H
4

I use the following code as alternative, and it works. And the variable can be array too. (@ 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]
Hollah answered 17/6, 2016 at 1:23 Comment(0)
C
3

The other answers posted here are better but here's an alternative that:

  • Sets the value at initialization (not a getter, or derived, etc)
  • Doesn't require any type of init() or code outside of the object literal
  • Is an object literal and not a factory function or other object creation mechanic.
  • Shouldn't have any performance impact (except at initialization)

Self-executing anonymous functions and window storage

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

The order is guaranteed (bar before baz).

It pollutes window of course, but I can't imagine someone writing a script that requires window.temp to be persistent. Maybe tempMyApp if you're paranoid.

It's also ugly but occasionally useful. An example is when you are using an API with rigid initialization conditions and don't feel like refactoring so the scoping is correct.

And it's dry, of course.

Capper answered 24/3, 2015 at 21:6 Comment(0)
Q
3

Here is an example of behavior of 'this' in the object.

this.prop = 'external';
global.prop = 'global.prop';
const that = this;

const a = {
  prop: 'internal',
  prop1: this.prop, //external

  log() {
    return this.prop //internal
  },
  log1: () => {
    return this.prop //external
  },
  log2: () => {
    return function () {
      return this.prop; //'global.prop' in node; 'external' in chrome
    }()
  },
  log3: function () {
    return (() => {
      return this.prop; //internal
    })()
  },
}
Quodlibet answered 6/11, 2021 at 21:18 Comment(0)
C
2

The key to all this is SCOPE.

You need to encapsulate the "parent" (parent object) of the property you want to define as it's own instantiated object, and then you can make references to sibling properties using the key word this

It's very, very important to remember that if you refer to this without first so doing, then this will refer to the outer scope... which will be the window object.

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)
};
Canning answered 7/5, 2014 at 10:15 Comment(0)
C
2

if your object is written as a function which returns an object, AND you use ES6 object-attribute 'methods', then it's 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);
Census answered 13/8, 2016 at 15:40 Comment(0)
F
2

Here's a neat ES6 way:

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

I use it to do something like this:

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]);
Freewheeling answered 12/6, 2018 at 23:16 Comment(0)
P
1

How about this solution this will work with nested objects with array as well

      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');
Pitzer answered 21/11, 2016 at 18:47 Comment(0)
F
1

Throwing in an option since I didn't see this exact scenario covered. If you don't want c updated when a or b update, then an ES6 IIFE works well.

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

For my needs, I have an object that relates to an array which will end up being used in a loop, so I only want to calculate some common setup once, so this is what I have:

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

Since I need to set a property for indexOfSelectedTier and I need to use that value when setting the hasUpperTierSelection property, I calculate that value first and pass it in as a param to the IIFE

Floe answered 13/2, 2018 at 19:14 Comment(0)
A
1

I think following is best code for maintanability even though it's not in object literal syntax:

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

This creates a new empty object with {} and then uses the anonymous function to set its properties (executed with call()). I think the only bad part is the need for return this which feels like one extra line of code. Unfortunately, I cannot figure out any nicer way to move the reference to newly created anonymous object to foo.

I think this is better than syntax var foo = new function() {...} because this one doesn't create one extra level in the prototype chain as explained by @Bergi in the comments in one of the existing answers.

However, if this is truly literal without any other logic but one addition, it would make more sense to just write

const foo = {
    a:5,
    b:6,
    c:11, // sum of a + b
};

because there's no need to calculate that sum during runtime or even compile time.

Auer answered 29/9, 2022 at 12:8 Comment(0)
A
1

Alternative syntax with pretty good maintanability:

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

This works because JavaScript will use the variable name as the property name for the newly created object if you don't specify the name explicitly. For a short array like this, I'd personally go with single line syntax with return if this were inside a function:

let a = 5;
let b = 6;
return { a, b, c:a+b };
Auer answered 30/9, 2022 at 7:30 Comment(0)
M
0

Two lazy solutions

There are already excellent answers here and I'm no expert on this, but I am an expert in being lazy and to my expert eye these answers don't seem lazy enough.

First: return object from anonymous function

A very slight variation from T.J. Crowder, Henry Wrightson and Rafael Rocha answers:

var foo = (() => {

  // Paste in your original object
  const foo = {
    a: 5,
    b: 6,
  };
  
  // Use their properties
  foo.c = foo.a + foo.b;

  // Do whatever else you want

  // Finally, return object
  return foo;
})();

console.log(foo);

The slight advantage here is just pasting your original object as it was, without worrying about arguments etc. (IMHO the wrapper function becomes quite transparent this way).

Second: using setTimeout

This here may work, if you don't need foo.c right away:

var foo = {
  a: 5,
  b: 6,
  c: setTimeout(() => foo.c = foo.a + foo.b, 0)
};

// Though, at first, foo.c will be the integer returned by setTimeout
console.log(foo);
// But if this isn't an issue, the value will be updated when time comes in the event loop
setTimeout( () => console.log(foo), 0);
Mandal answered 5/1, 2021 at 18:31 Comment(0)
F
0

Ok I came up with another solution. Here I want to initialize an object representing the amount of milliseconds for each unit of time. It turns out enum in typescript can not be used in my case, so I declared multiple variables that I assign to an object as follow:

const SECOND = 1000
const MINUTE = 60 * SECOND
const HOUR = 60 * MINUTE
const DAY = 24 * HOUR
const WEEK = 7 * DAY

export const TimeInMS = {
  SECOND,
  MINUTE,
  HOUR,
  DAY,
  WEEK
}

The drawbacks of this method are:

  • variables are defined as constants, even if we dont need them. Thus it needs useless memory.
  • each value of the object must be declared as standalone variable
Franko answered 5/7, 2022 at 13:19 Comment(0)
I
0

Although an object declaration does not allow you to reference prior properties, the default values in a function declaration do.

Therefore, you can do this:

const foo = ((a=5, b=6, c=a+b) => ({a,b,c}))()

console.log(foo)
Inextricable answered 9/3, 2023 at 21:18 Comment(0)
I
-1

Note: This solution uses Typescript (you can use the vanilla JS which TS compiles to if needed)

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

Here were using class expressions to get the nested object literal interface we'd want. This is the next best thing IMHO to being able to reference the properties of an object during creation.

Main thing to note is while using this solution, you have exact same interface as you'd have had from an object literal. And the syntax is pretty close to an object literal itself (vs using a function, etc).

Compare the following

Solution I've proposed

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

Solution if object literals would've sufficed

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

Another example

Here in this class, you can combine multiple relative path among themselves, which is not possible with an object 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';
        };
    }
}
Iconostasis answered 19/6, 2018 at 13:33 Comment(3)
Could it be because your answer is totally irrelevant? I agree that downvoters should explain, but your answer clear has nothing to do with the question …Lone
@Lone thanks for pointing it out. Honestly, I'd the same question as OP and I use solution I've suggested. Unsure, why it's being considered irrelevant. If you do have the time, please explain so I can make the answer better or at least know where I'm wrong. I'm unfortunately, not understanding why this is not a reasonable solution.Iconostasis
This doesn't address the problem of self reference at all. What you proposed is a rather convoluted way of emulating self reference by introducing unnecessary closure in your code.Aniseed
B
-1

If you want to use native JS, the other answers provide good solutions.

But if you're willing to write self-referencing objects like:

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

I wrote an npm library called self-referenced-object that supports that syntax and returns a native object.

Badinage answered 27/4, 2019 at 8:56 Comment(5)
Please avoid link only answers. Answers that are "barely more than a link to an external site” may be deleted.Norris
@Norris do you have any suggestions on how I can improve my answer? The other answers to this question cover how you might be able to write self-referencing objects in native javascript, but if you want to write self-referencing objects with a syntax similar to the syntax in the posters original question I think that the library that I wrote may be useful to others looking for a solution. Happy to get some feedback.Badinage
Couple things to improve here. First, and most obviously, you're using template literal syntax without back ticks. Your b property's value should be: ${this.a + this.a}. Second, but less importantly, you'd want to return a number, not a string by using something like parseInt. Last and MOST importantly, when I tried this example it simply doesn't work, for the same reason the OP is asking. The this returns undefined when used its own object declaration. @BadinageFirebug
@AlecDonaldMather - thanks for taking the time to take a look and provide some feedback! If you're interested in the project it might be better to move this discussion over to github, but to answer some of your feedback: - Using backticks: As mentioned in previous comments, this syntax isnt supported by JS, and so using strings instead of backticks is required here to avoid js trying to resolve "this" before the obj has been defined - returning a number, this should work if a + b are already numbers, since a + b will return a number if both a and b are already numbers.Badinage
Re this returning undefined, can you explain how you tried to use the library? This shouldn't happen, but maybe there's an edge case I've left out? That said, this library doesn't solve the issue completely, and has it's own set of tradeoffs, but if you're interested in helping me to improve it/use it let me know!Badinage
S
-1

Other approach would be to declare the object first before assigning properties into it:

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

With that, you can use the object variable name to access the already assigned values.
Best for config.js file.

Steinmetz answered 9/6, 2019 at 15:40 Comment(1)
That isn't a self reference, but rather a reference to the declared variable foo which points to the object in question.Accordingly
O
-1

If you want to avoid using the this keyword, you can use my ES6 approach below

const MEASUREMENT = {
  device_width: 124,
  canvas_padding: 24,
  full_width: () => MEASUREMENT.device_width - MEASUREMENT.canvas_padding,
};
Oscoumbrian answered 13/9, 2023 at 13:23 Comment(0)
P
-4
var x = {
    a: (window.secreta = 5),
    b: (window.secretb = 6),
    c: window.secreta + window.secretb
};

This is almost identical to @slicedtoad's answer, but doesn't use a function.

Polanco answered 31/10, 2019 at 8:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.