Skip to content
This repository has been archived by the owner on Jan 25, 2022. It is now read-only.

Context of "this" in properties definition #62

Closed
syabro opened this issue Dec 5, 2017 · 11 comments
Closed

Context of "this" in properties definition #62

syabro opened this issue Dec 5, 2017 · 11 comments

Comments

@syabro
Copy link

syabro commented Dec 5, 2017

I've created a bug here babel/babel#6977 about ambiguous behavior of this in property definition, but looks like it could be not a bug.

Provide an example code here

class A  {
  state = { value: this.value }

  constructor(val) {
    this.value = val
  }
}

console.log('A.state.value', new A('A').state.value)


class BParent  {
  constructor(val) {
    this.value = val
  }
}

class B extends BParent {
  state = {value: this.value}
}

console.log('B.state.value', new B('B').state.value)

will produce

A.state.value undefined
B.state.value B

I can't find anything about context for this in https://tc39.github.io/proposal-class-fields/

Am I missing something? What's the expected behavior? For me using this in class properties must be restricted.

@syabro
Copy link
Author

syabro commented Dec 5, 2017

UPD: @kovensky said

@syabro that is done by Step 8.a of the [[Construct]] internal method. The initializers are only called on Step 8.b.

So it's not a bug but just an ambiguous behavior?

@Jessidhia
Copy link

Jessidhia commented Dec 5, 2017

Here is the (abridged) chain of events for calling new A:

  • Inside [[Construct]]
    • Step 8: kind is "base"
      • Step 8.a: Bind a this in the environment record. This is going to be the this that the constructor executes with. This turns out to not actually be relevant to the next step.
      • Step 8.b: Call InitializeInstanceFields with the new instance and the constructor as its arguments.
  • Inside InitializeInstanceFields(newInstance, constructor)
    • Look at all [[Fields]] saved in the constructor and call DefineField for each one with newInstance and each fieldRecord as arguments
  • Inside DefineField(receiver, fieldRecord)
    • If the fieldRecord has an initializer, call the initializer with newInstance as its this value.
      • Initializers are modeled as functions with no argument and implicit return. What you write on the right side of the = is what is evaluated as the return's expression.
      • Call(initializer, receiver) ensures that the initializer runs with a valid this.

It's a bit more complicated when calling new B, but the chain of events is more like:

  • Inside [[Construct]]
    • Step 8: kind is not "base"
    • Step 11: the constructor function is called
  • Inside the constructor function:
    • It must call super at some point, unless it returns some unrelated object
      • An implicit constructor will call super for you
    • If it never calls super, the initializers are never run. You would get a ReferenceError on [[Construct]]'s Step 15 unless you specifically return another object, which would have entered Step 13.
  • Inside super evaluation
    • Will evaluate the superclass' constructor to completion, including the superclass' own field definitions and other possible ancestor super calls
    • Just before binding the this value for use in the rest of the constructor function, InitializeInstanceFields is called with the new this value as the new instance.
    • Everything else proceeds as above

@Jessidhia
Copy link

What is happening on your example, and what feels like it is ambiguous, is that the superclass' constructor will run to completion before any of your fields are initialized. Because, instead of your own constructor, it is the superclass that is assigning to this.value, new B will have a this.value by the time its state initializer is evaluated.

If you did the assignment on your own constructor on both cases, the result would be identical.

@syabro
Copy link
Author

syabro commented Dec 5, 2017

@Kovensky for me overriding this's value is the problem. So for me two options are acceptable

  • doesn't change this
  • forbid to use it in class properties

@Jessidhia
Copy link

Forbidding the use of this in class properties would also imply forbidding the use of arrow functions in class properties.

The value of this in initializers is set and doesn't "change". It is always the object being constructed. The reason this.value is different in your example is not because this is different, it's because the superclass' constructor got to write to this.value before your own class' initializer got to read it.

@Jessidhia
Copy link

A real world example where reading from this is very useful is in React components.

export default class ExpandableParagraph extends React.Component {
  state = {
    expanded: this.props.defaultExpanded || false
  }
  // ...
}

This allows you to correctly initialize this.state, when it has a data dependency on your components' props, without needing to write your own constructor and call super with the right number of arguments.

@syabro
Copy link
Author

syabro commented Dec 5, 2017

@Kovensky changing context for things in arrow functions is straight.

class A  {
  state = { value: this.value }
}

must be referred to this in context of class A declaration f.e. window

@syabro
Copy link
Author

syabro commented Dec 5, 2017

@Kovensky Now I understand how does it works and how can we use it
But between vague and a little bit more usable vs clear and bit less usable I prefer use the second.

@Jessidhia
Copy link

I see now what your confusion were w.r.t. what this should be on A's initilalizers.

This was previously discussed in tc39/proposal-class-public-fields#34; the consensus is that it should be the object being currently constructed.

@syabro
Copy link
Author

syabro commented Dec 5, 2017

@Kovensky thanks!

@syabro syabro closed this as completed Dec 5, 2017
@littledan
Copy link
Member

Thanks for the great descriptions @Kovensky and for raising the issue in the first place @syabro . We did discuss the this value being potentially a little unclear; the overriding factor was its value and the analogy of this to what it is in methods.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants