autobind-decorator icon indicating copy to clipboard operation
autobind-decorator copied to clipboard

If bound methods or fields are re-assigned, they become shared between instances

Open betalb opened this issue 5 years ago • 8 comments

If bound methods or fields are re-assigned, they become shared between instances

Found this issue while trying to debounce bound method

Below test will fail

class A {
  @boundMethod
  bound() {}
}

const a = new A();
const b = new A();
a.bound = {
  foo: 'bar'
};
assert.notEqual(a.bound, b.bound);

betalb avatar Nov 16 '18 13:11 betalb

Committed failing test cases and fix for @boundMethod decorator

betalb avatar Nov 16 '18 13:11 betalb

Does it happen if you use @autobind (the old default exported module)?

stevemao avatar Nov 17 '18 03:11 stevemao

Yes, latest version before exporting of boundMethod and boundClass separately had this issue Looks like previous commit introduced it https://github.com/andreypopp/autobind-decorator/commit/98968eea6aadce8234aa95e6560239e9e11be146

Before this commit new function will just override bound function, but it won't be re-bound later on. Above comit tried to overcome this by deleting original descriptor and letting binding to happen again, but this time binding will be done on function that might be instance-specific, because we are working with instance, not prototype in this case.

In my case I was taking already bound function and debounced it

betalb avatar Nov 18 '18 16:11 betalb

@betalb Thanks for reporting this. PR welcome :)

stevemao avatar Nov 18 '18 23:11 stevemao

Created PR, but would like to revise it further, don't like appoach for super.method handling

betalb avatar Nov 20 '18 12:11 betalb

Any movement on this?

L-Yeiser avatar Jan 23 '19 22:01 L-Yeiser

@L-Yeiser see discussions in the PR

stevemao avatar Jan 23 '19 22:01 stevemao

I'd like to add, that there is also a possibility of memory leak.

Minimal code to illustrate:

class Base extends React.Component {
  @boundMethod
  method() {}
}

class Child extends Base {
  method = debounce(super.method, 100);
}
  1. When constructing new Child(), the assignment method = debounce(super.method, 100) is executed
  2. The assignment method = debounce(super.method, 100) will get bound method, then debounce it, and finally will call setter
  3. The setter stores debounced bound method for all future instances.
  4. When constructing new Child() second time, the code method = debounce(super.method, 100) will receive bound debounced bound method, debounce it and store again.
  5. Each new instance will add its own level of bind + debounce and store the result for future instances.

bind and debounce consume memory themselves, but they also retain in memory their closures with all accessible variables.

Reproduction: https://codesandbox.io/s/autobind-debounce-memory-leak-jezks?file=/src/Child.js

What it looks like in DevTools: memory leak

So, can you fix the bug and the memory leak in two years?

victor-homyakov avatar Oct 30 '20 13:10 victor-homyakov