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.

2 Likes

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).

4 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.

2 Likes

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

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.

I think, exactly because of this, there should be a proper syntax for it... Almost no one knows about this and if you try to explain it to people they think it will make their code run slower and is deprecated.

Another thing is that { __proto__: MyOtherObject } is not recognized by any type inference engine out there except TernJS. Having a proper syntax that doesn't look like a hack, hopefully would improve this and get these sorts of tools to properly adopt it.

1 Like

I agree with @TitanNano here. The __proto__ syntax is a dirty legacy hack, and it totally is a footgun.

> j = JSON.parse('{"__proto__":{"x":42}}');
{ ['__proto__']: { x: 42 } }

> o = { ...j };
{ ['__proto__']: { x: 42 } }
> Object.getPrototypeOf(o);
[Object: null prototype] {}

> o = Object.assign({}, j);
{}
> Object.getPrototypeOf(o);
{ x: 42 }

I was pondering different ways to replace the __proto__ quirk before finding this thread. So far I haven't been able to come up with a syntax I'd immediately like, but here are some alternatives for consideration:

n = { null, ... };
// very concise, but has issues:
//   1) someone might read it as { "null": null }
//   2) only works for null
n = { (null), ... };
o = { (Klas.prototype), ... };
n = new null { ... }; // no line break before {
o = new Object.prototype { ... };

We could reuse the extends keyword:

n = { extends null, ... };
o = { extends Klas.prototype, ... };
// or
o = { ... } extends Klas.prototype;
n = { ... } extends null;
// although I don't like the latter, because I want the prototype in front;
// otherwise there's little benefit over Object.setPrototypeOf({ ... }, P);

> Object.getPrototypeOf(n) === null
true
> Object.getPrototypeOf(o) === Klas.prototype
true

What I find unsatisfactory about these is that using .prototype after extends feels weird, and only starts making sense after you consider what extends does with classes:

class A {};
class B extends A {};

> Object.getPrototypeOf(B) === A
true
1 Like

I personally would like this syntax, because it's similar to how other languages do struct instances.

const Prototype = { a: 1, b: 2, doSomething() {} };
const a = Prototype { a: 3 };
const b = a { prop: true };

But I have no Idea if the existing ECMAScript grammar can accommodate it.

1 Like

There once was a proposal for a <| prototype operator which did just that:

const Prototype = { a: 1, b: 2, doSomething() {} };
const a = Prototype <| { a: 3 };
const b = a <| { prop: true }

In contrast to {__proto__: …}, it would work not just with object literals but also array, function or regex literals.

3 Likes

It can, but that would eliminate the possibility for any future keyword {} syntax, like e.g. module blocks

The thing you're pointing at in that example is the __proto__ setter, not the __proto__ syntax.

2 Likes

Thanks for clarification, I conflated the two things. __proto__ syntax in object literal is not the footgun. Still hate it, though.

1 Like