Object Literal Extensions

ES6 adds a number of important convenience extensions to the humble { .. } object literal.

Concise Properties

You’re certainly familiar with declaring object literals in this form:

  1. var x = 2, y = 3,
  2. o = {
  3. x: x,
  4. y: y
  5. };

If it’s always felt redundant to say x: x all over, there’s good news. If you need to define a property that is the same name as a lexical identifier, you can shorten it from x: x to x. Consider:

  1. var x = 2, y = 3,
  2. o = {
  3. x,
  4. y
  5. };

Concise Methods

In a similar spirit to concise properties we just examined, functions attached to properties in object literals also have a concise form, for convenience.

The old way:

  1. var o = {
  2. x: function(){
  3. // ..
  4. },
  5. y: function(){
  6. // ..
  7. }
  8. }

And as of ES6:

  1. var o = {
  2. x() {
  3. // ..
  4. },
  5. y() {
  6. // ..
  7. }
  8. }

Warning: While x() { .. } seems to just be shorthand for x: function(){ .. }, concise methods have special behaviors that their older counterparts don’t; specifically, the allowance for super (see “Object super“ later in this chapter).

Generators (see Chapter 4) also have a concise method form:

  1. var o = {
  2. *foo() { .. }
  3. };

Concisely Unnamed

While that convenience shorthand is quite attractive, there’s a subtle gotcha to be aware of. To illustrate, let’s examine pre-ES6 code like the following, which you might try to refactor to use concise methods:

  1. function runSomething(o) {
  2. var x = Math.random(),
  3. y = Math.random();
  4. return o.something( x, y );
  5. }
  6. runSomething( {
  7. something: function something(x,y) {
  8. if (x > y) {
  9. // recursively call with `x`
  10. // and `y` swapped
  11. return something( y, x );
  12. }
  13. return y - x;
  14. }
  15. } );

This obviously silly code just generates two random numbers and subtracts the smaller from the bigger. But what’s important here isn’t what it does, but rather how it’s defined. Let’s focus on the object literal and function definition, as we see here:

  1. runSomething( {
  2. something: function something(x,y) {
  3. // ..
  4. }
  5. } );

Why do we say both something: and function something? Isn’t that redundant? Actually, no, both are needed for different purposes. The property something is how we can call o.something(..), sort of like its public name. But the second something is a lexical name to refer to the function from inside itself, for recursion purposes.

Can you see why the line return something(y,x) needs the name something to refer to the function? There’s no lexical name for the object, such that it could have said return o.something(y,x) or something of that sort.

That’s actually a pretty common practice when the object literal does have an identifying name, such as:

  1. var controller = {
  2. makeRequest: function(..){
  3. // ..
  4. controller.makeRequest(..);
  5. }
  6. };

Is this a good idea? Perhaps, perhaps not. You’re assuming that the name controller will always point to the object in question. But it very well may not — the makeRequest(..) function doesn’t control the outer code and so can’t force that to be the case. This could come back to bite you.

Others prefer to use this to define such things:

  1. var controller = {
  2. makeRequest: function(..){
  3. // ..
  4. this.makeRequest(..);
  5. }
  6. };

That looks fine, and should work if you always invoke the method as controller.makeRequest(..). But you now have a this binding gotcha if you do something like:

  1. btn.addEventListener( "click", controller.makeRequest, false );

Of course, you can solve that by passing controller.makeRequest.bind(controller) as the handler reference to bind the event to. But yuck — it isn’t very appealing.

Or what if your inner this.makeRequest(..) call needs to be made from a nested function? You’ll have another this binding hazard, which people will often solve with the hacky var self = this, such as:

  1. var controller = {
  2. makeRequest: function(..){
  3. var self = this;
  4. btn.addEventListener( "click", function(){
  5. // ..
  6. self.makeRequest(..);
  7. }, false );
  8. }
  9. };

More yuck.

Note: For more information on this binding rules and gotchas, see Chapters 1-2 of the this & Object Prototypes title of this series.

OK, what does all this have to do with concise methods? Recall our something(..) method definition:

  1. runSomething( {
  2. something: function something(x,y) {
  3. // ..
  4. }
  5. } );

The second something here provides a super convenient lexical identifier that will always point to the function itself, giving us the perfect reference for recursion, event binding/unbinding, and so on — no messing around with this or trying to use an untrustable object reference.

Great!

So, now we try to refactor that function reference to this ES6 concise method form:

  1. runSomething( {
  2. something(x,y) {
  3. if (x > y) {
  4. return something( y, x );
  5. }
  6. return y - x;
  7. }
  8. } );

Seems fine at first glance, except this code will break. The return something(..) call will not find a something identifier, so you’ll get a ReferenceError. Oops. But why?

The above ES6 snippet is interpreted as meaning:

  1. runSomething( {
  2. something: function(x,y){
  3. if (x > y) {
  4. return something( y, x );
  5. }
  6. return y - x;
  7. }
  8. } );

Look closely. Do you see the problem? The concise method definition implies something: function(x,y). See how the second something we were relying on has been omitted? In other words, concise methods imply anonymous function expressions.

Yeah, yuck.

Note: You may be tempted to think that => arrow functions are a good solution here, but they’re equally insufficient, as they’re also anonymous function expressions. We’ll cover them in “Arrow Functions” later in this chapter.

The partially redeeming news is that our something(x,y) concise method won’t be totally anonymous. See “Function Names” in Chapter 7 for information about ES6 function name inference rules. That won’t help us for our recursion, but it helps with debugging at least.

So what are we left to conclude about concise methods? They’re short and sweet, and a nice convenience. But you should only use them if you’re never going to need them to do recursion or event binding/unbinding. Otherwise, stick to your old-school something: function something(..) method definitions.

A lot of your methods are probably going to benefit from concise method definitions, so that’s great news! Just be careful of the few where there’s an un-naming hazard.

ES5 Getter/Setter

Technically, ES5 defined getter/setter literals forms, but they didn’t seem to get used much, mostly due to the lack of transpilers to handle that new syntax (the only major new syntax added in ES5, really). So while it’s not a new ES6 feature, we’ll briefly refresh on that form, as it’s probably going to be much more useful with ES6 going forward.

Consider:

  1. var o = {
  2. __id: 10,
  3. get id() { return this.__id++; },
  4. set id(v) { this.__id = v; }
  5. }
  6. o.id; // 10
  7. o.id; // 11
  8. o.id = 20;
  9. o.id; // 20
  10. // and:
  11. o.__id; // 21
  12. o.__id; // 21 -- still!

These getter and setter literal forms are also present in classes; see Chapter 3.

Warning: It may not be obvious, but the setter literal must have exactly one declared parameter; omitting it or listing others is illegal syntax. The single required parameter can use destructuring and defaults (e.g., set id({ id: v = 0 }) { .. }), but the gather/rest ... is not allowed (set id(...v) { .. }).

Computed Property Names

You’ve probably been in a situation like the following snippet, where you have one or more property names that come from some sort of expression and thus can’t be put into the object literal:

  1. var prefix = "user_";
  2. var o = {
  3. baz: function(..){ .. }
  4. };
  5. o[ prefix + "foo" ] = function(..){ .. };
  6. o[ prefix + "bar" ] = function(..){ .. };
  7. ..

ES6 adds a syntax to the object literal definition which allows you to specify an expression that should be computed, whose result is the property name assigned. Consider:

  1. var prefix = "user_";
  2. var o = {
  3. baz: function(..){ .. },
  4. [ prefix + "foo" ]: function(..){ .. },
  5. [ prefix + "bar" ]: function(..){ .. }
  6. ..
  7. };

Any valid expression can appear inside the [ .. ] that sits in the property name position of the object literal definition.

Probably the most common use of computed property names will be with Symbols (which we cover in “Symbols” later in this chapter), such as:

  1. var o = {
  2. [Symbol.toStringTag]: "really cool thing",
  3. ..
  4. };

Symbol.toStringTag is a special built-in value, which we evaluate with the [ .. ] syntax, so we can assign the "really cool thing" value to the special property name.

Computed property names can also appear as the name of a concise method or a concise generator:

  1. var o = {
  2. ["f" + "oo"]() { .. } // computed concise method
  3. *["b" + "ar"]() { .. } // computed concise generator
  4. };

Setting [[Prototype]]

We won’t cover prototypes in detail here, so for more information, see the this & Object Prototypes title of this series.

Sometimes it will be helpful to assign the [[Prototype]] of an object at the same time you’re declaring its object literal. The following has been a nonstandard extension in many JS engines for a while, but is standardized as of ES6:

  1. var o1 = {
  2. // ..
  3. };
  4. var o2 = {
  5. __proto__: o1,
  6. // ..
  7. };

o2 is declared with a normal object literal, but it’s also [[Prototype]]-linked to o1. The __proto__ property name here can also be a string "__proto__", but note that it cannot be the result of a computed property name (see the previous section).

__proto__ is controversial, to say the least. It’s a decades-old proprietary extension to JS that is finally standardized, somewhat begrudgingly it seems, in ES6. Many developers feel it shouldn’t ever be used. In fact, it’s in “Annex B” of ES6, which is the section that lists things JS feels it has to standardize for compatibility reasons only.

Warning: Though I’m narrowly endorsing __proto__ as a key in an object literal definition, I definitely do not endorse using it in its object property form, like o.__proto__. That form is both a getter and setter (again for compatibility reasons), but there are definitely better options. See the this & Object Prototypes title of this series for more information.

For setting the [[Prototype]] of an existing object, you can use the ES6 utility Object.setPrototypeOf(..). Consider:

  1. var o1 = {
  2. // ..
  3. };
  4. var o2 = {
  5. // ..
  6. };
  7. Object.setPrototypeOf( o2, o1 );

Note: We’ll discuss Object again in Chapter 6. “Object.setPrototypeOf(..) Static Function” provides additional details on Object.setPrototypeOf(..). Also see “Object.assign(..) Static Function” for another form that relates o2 prototypically to o1.

Object super

super is typically thought of as being only related to classes. However, due to JS’s classless-objects-with-prototypes nature, super is equally effective, and nearly the same in behavior, with plain objects’ concise methods.

Consider:

  1. var o1 = {
  2. foo() {
  3. console.log( "o1:foo" );
  4. }
  5. };
  6. var o2 = {
  7. foo() {
  8. super.foo();
  9. console.log( "o2:foo" );
  10. }
  11. };
  12. Object.setPrototypeOf( o2, o1 );
  13. o2.foo(); // o1:foo
  14. // o2:foo

Warning: super is only allowed in concise methods, not regular function expression properties. It also is only allowed in super.XXX form (for property/method access), not in super() form.

The super reference in the o2.foo() method is locked statically to o2, and specifically to the [[Prototype]] of o2. super here would basically be Object.getPrototypeOf(o2) — resolves to o1 of course — which is how it finds and calls o1.foo().

For complete details on super, see “Classes” in Chapter 3.