Skip to content

The "initializer" property of a descriptor for a plain object #41

@fatfisz

Description

@fatfisz

Just for the record: while writing this I came to a conclusion that "all" of my problems would be solved if I only have used the decorators in the Python way, i. e. only for classes and methods, and not for properties. I'm still leaving my thoughts though, as maybe they'll prove useful in one way or the other.

I have a problem with the temporary initializer descriptor property.

I wanted to decorate a property of a plain object and found out that babel is doing something which seemed strange to me. The value descriptor property was nowhere to be found when my decorator was called, and instead I've found an initializer property on the descriptor. That led me to this spec and the spec about class fields. After finding no clues as to why should that property even exist when I was dealing with a plain object, I've first created an issue in the babel repo, and then turned here.

I'll now present my thoughts on the matter of the issue. Please be gentle with me.

First, let's look at a simple object.

let person = {
  born: Date.now()
};

If we'd really like to use the Object.defineProperty function to highlight what's going on with the descriptor, it would look like this:

let person = {};
Object.defineProperty(person, 'born', {
  value: Date.now(),
  enumerable: true,
  writable: true,
  configurable: true,
};

I don't really see a reason to introduce another descriptor property here.

Now, the way a decorator would handle this if we didn't know classes existed is quite straightforward - just do the same thing as with the methods. But, enter classes - and boom, everything has to be changed. I know that in reality we have classes, but I wanted to take you to a different world for a second to provide another perspective.

Let's take a look at the classes now.
I've read the es-class-static-properties-and-fields spec and didn't find any clues to the initializer property. That's why I was puzzled by the example in INITIALIZER_INTEROP.md from this repo:

class Person {
  born = Date.now();
}

// desugars to roughly:

class Person {
  constructor() {
    let _born = Object.getPropertyDescriptor(this, 'born');
    this.born = _born.initializer.call(this);
  }
}

Object.defineProperty(Person.prototype, 'born', {
  initializer() { return Date.now() },
  enumerable: true,
  writable: true,
  configurable: true,
});

I thought that doing it this way didn't match what was described in the class fields spec. So I've decided to try and do it in another way:

class Person {
  constructor() {
    Object.defineProperty(this, 'born', {
      value: initializer.call(this),
      enumerable: true,
      writable: true,
      configurable: true,
    });
  }
}

let initializer = function () {
  return Date.now();
};

This solution doesn't clutter the prototype and otherwise behaves like the original solution - has the proper scoping and the initializer is called properly. Then I tried adding a decorator to my solution:

class Person {
  @readonly
  born = Date.now();
}

// is desugared to

class Person {
  constructor() {
    let desc = {
      value: initializer.call(this),
      enumerable: true,
      writable: true,
      configurable: true,
    };
    desc = readonly(this, 'born', desc) || desc;
    Object.defineProperty(this, 'born', desc);
  }
}

let initializer = function () {
  return Date.now();
};

Things here differ from the original solution a bit more than in the previous situation:

  • the decorator is called with this as a target and not with a prototype - that seems more correct to me, as the property is being assigned to the instantiated object and not the prototype, so this seems more proper as a "target"
  • the decorator is applied each time the constructor is ran, instead of applying it only once. But then the field that is decorated isn't "applied" only once - its expression is evaluated each time an object is instantiated, so the decorator behavior is more in sync with what's happening with the field

I think that decorators should operate on the standard descriptor because it's possible, as I've demonstrated above.

Those are my honest thoughts. If there was already a similar discussion about these problems, please point me there.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions