Prototype definition in object literal

The gist of the idea is to allow specifying the class/prototype of the object created via object literal syntax. Currently, any object created via object literal is an instance of built-in Object class:

const example = {
  foo: 'bar'
};

so when you want to create a lightweight object, you write something like this:

const example = Object.create(null);
example.foo = 'bar';

I can't tell how this affects the property access optimization, I believe either very minorly or it doesn't affect it at all. The idea is to use syntax like this to specify the class and properties of the object, created via object literal:

//lightweight object
const example:null = {
  foo: 'bar'
};

//Normal object
const example1 = {
  foo: 'bar'
};

const example2:Object = {
  foo: 'bar'
};

//User-defined class
const example:MyClass = {
  foo: 'bar'
};
1 Like

This is actually possible since the early days of Javascript. It's just not a well-known feature and is a little unintuitive (so it could be nice to have better syntax for it)

But, you just have to set the special "__proto__" property as you're creating the object, and that value will be set to the object's prototype.

For example:

//lightweight object
const example = {
  __proto__: null,
  foo: 'bar'
};

//User-defined class
const example = {
  __proto__: MyClass.prototype,
  foo: 'bar'
};

MDN talks a little about it on this page.

1 Like

Yep. The gist of this post is all about the syntax. As far as I remember, the __proto__ property is not a part of the spec, so maybe it would make sense to come up with some standard syntax for this, with fewer magic properties?

It’s absolutely part of the spec, since ES2015. It’s in the process of being moved out of Annex B, as well, so all implementations would be required to implement it (all do anyways).

3 Likes

It might be hard to justify new syntax for it, since syntax already exists, and there's a high bar for adding new syntax to the language (to prevent it from becoming overly complicated). The current syntax doesn't really create any footguns either, it's just odd to use, especially since any google search about the __proto__ property will bring you with results talking about how getting or setting __proto__ on a property is deprecated (though they usually don't mention that __proto__ in an object literal isn't depracated).

I usually avoid __proto__ for that reason, and do one of these two solutions to create an object with a prototype.

Object.assign(Object.create(null), { foo: 'bar' })
// or
Object.create(null, Object.getOwnPropertyDescriptors({ foo: 'bar' }))

These are a little more verbose, but I'm ok with that verbosity considering I don't do this kind of thing a ton. But I know some other Javascript coding styles make use of this kind of thing a lot more.

The only justifications that come to my mind are neater syntax and subtle (if any) improvements to object property access speeds, assuming that @ theScottyJam's example doesn't solve this problem.

Current ways to achieve the same look quite ugly, but I'm an artist, that's how I see it :man_artist:.

1 Like

I think an object should always has a prototype. It is useful for debugging.
Map should be used instead of using a "prototypeless" object if you don't want additional keys in the prototype chain.

That's certainly debatable.

I much prefer null prototypes, as there's much better support for working with objects than maps (both with syntax and with helper functions). Compare

map[key] ??= 0
may[key]++

with

if (!map.has(key)) {
  map.set(key, 0)
}
map.set(key, map.get(key) + 1)

I do, however, understand the hesitance with them, and understand when people choose to avoid them. But, I don't necessarily think the missing .toString() is a big enough reason to have everyone shun them (though you may disagree, which is fine).

Nit: Map.prototype.emplace attempts to resolve that pretty elegantly.

map.emplace(key, {
  insert: () => 1,
  update: x => x + 1
})
1 Like

I suggest a new method Object.from(obj), which returns a new object having same keys and prototype of input object.

const example = Object.create(null);
example.foo = 'bar';

const example1 = Object.from(example); // example1 and example having same 'Shape'
example1.foo = 'bar1';

You could get pretty far and call it a day:

// Your proposed semantics
Object.from = source =>
    ({__proto__: Object.getPrototypeOf(source), ...source})

Given how niche that is, I'm not convinced it'll get to even stage 1, much less stage 4.

Another reason of introducing Object.from(obj) is to provide a better way to maintain the 'Shape' (as mentioned at the property access optimization)of objects.
e.g.

const example1 = {
  foo: 'bar1'
};

const example2 = {
  foo: 'bar2'
};
// example1 and example2 are optimized

but later, you change one object

const example1 = {
  foo: 'bar1',
  baz: 'baz'
};

const example2 = {
  foo: 'bar2'
};
// example1 and example2 are now not optimized

I'm not sure I follow your thought process yet. Could you expound on how you would use Object.from() to help with property access optimization?

What about the performance of __proto__? I tried to check the performance vs Object.create(), but I struggle interpreting results (uncomment commented lines and __proto__ will show the best result).

It may be an off topic, but I'm curious why and when construction functions and new operator were preferred to syntax like this.