Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Difference between mobx's `action.bound` and arrow functions on class functions?

Using arrow functions on a class with babel transpiles it so the definition is bound in the constructor. And so it is not in the prototype and it is not available via super when inheriting. It is also not as efficient when scaling by creating many instances.

There are more blog posts on this topic, but I just wanted to know the difference in how mobx.action.bound is handled compared to arrow functions when using babel.

Comparing the two:

class Example {
   test = () => {
      console.log(this.message)
   }
}

class Example {
   @action.bound
   test() {
      console.log(this.message)
   }
}
like image 485
bitten Avatar asked Feb 06 '18 09:02

bitten


1 Answers

There are 2 variables @action and @action.bound have an effect on:

  1. Binding: How this is bound in the resulting function.
  2. Prototype: If the resulting function is in the prototype.

To summarize, these are the rules:

  • @action preserves the original function's binding and prototype-inclusion. If the original function is not bound, the result will not be, and vise versa. And if the original function is not in the prototype, the result will not be, and vise versa.
  • @action.bound will always result in a function which is bound, and which is in the prototype.

How Binding is affected:

You can easily test this like so:

class Store {
  unbound() {
    console.log('unbound', this)
  }

  arrow = () => {
    console.log('arrow', this)
  }
}
const storeInstance = new Store()
const unbound = storeInstance.unbound
const arrow = storeInstance.arrow
unbound()
arrow()
// console displays:
// unbound undefined
// arrow Store

Now let's try adding @action:

class Store {
  @action
  unbound() {
    console.log('unbound', this)
  }

  @action
  arrow = () => {
    console.log('arrow', this)
  }
}
const storeInstance = new Store()
const unbound = storeInstance.unbound
const arrow = storeInstance.arrow
unbound()
arrow()
// console still displays:
// unbound undefined
// arrow Store

Now let's try adding @action.bound:

class Store {
  @action.bound
  unbound() {
    console.log('unbound', this)
  }

  @action.bound
  arrow = () => {
    console.log('arrow', this)
  }
}
const storeInstance = new Store()
const unbound = storeInstance.unbound
const arrow = storeInstance.arrow
unbound()
arrow()
// console now displays:
// unbound Store
// arrow Store

As you can see, @action maintains the function's bindings (or lack of binding). Meanwhile, @action.bound will always return a bound function, thus turning an unbound function into a bound one, and an already bound function will remain bounded.

How prototype is affected:

As for your concern about inheritance, here is the Store definition:

class Store {
  unbound() {}
  arrow = () => {}
  @action unboundAction() {}
  @action.bound unboundActionBound() {}
  @action arrowAction = () => {}      
  @action.bound arrowActionBound = () => {}
}

And this is what the storeInstance looks like: enter image description here

As you pointed out, arrow = () => {} is not part of the prototype. And to answer your question, @action arrow = () => {} will not result in a function which is in the prototype. It looks like @action preserves the previous behavior. However, @action.bound will always result in a function which is in the prototype.

like image 153
Croolsby Avatar answered Nov 06 '22 22:11

Croolsby