kenju's blog

About Programming, Mathematics, Security and Blockchain

bind()は常に新しいインスタンスを返却する

bind(this) とすると返却される関数は常に新しいインスタンスである。

stackoverflow.com

具体例:React Componentsのイベントハンドラーをbind()する場合

問題

たとえば、React Componentsを書いている時、イベントハンドラーを以下のようにbind()する場合がある。

export default class SomeComponent extends React.Component {
  componentDidMount() {
    window.addEventListener("resize", this.updateWindowSize.bind(this));
  }

  componentWillUnmount() {
    // bind(this) が異なるインスタンスを返却するため、"componentDidMount" と同じコールバックインスタンスを示しているわけではないため、リスナー解除できない
    window.removeEventListener("resize", this.updateWindowSize.bind(this));
  }

この場合、componentDidMount()componentWillUnmount()でadd/removeしている関数のインスタンスは実は別のインスタンスである。したがって、componentWillUnmount()内で正しくリスナーを解除できていない。

例えば、このComponentが複数回呼ばれるボトルネックになるようなクラスの場合、破棄されないリスナーのインスタンスが大量に発生し、エラーが発生する場合がある。

対応策

以下のように修正する必要がある:

export default class SomeComponent extends React.Component {
  constructor(props) {
    super(props);
    // constructor内でbindすることで、"this.updateWindowSize" が常に同じインスタンスであることを保証する
    this.updateWindowSize = this.updateWindowSize.bind(this);
  }

  componentDidMount() {
    window.addEventListener("resize", this.updateWindowSize);
  }

  componentWillUnmount() {
    window.removeEventListener("resize", this.updateWindowSize);
  }

constructor内でbindすることで、this.updateWindowSize() が常に同じインスタンスを指すことを保証する。つまり、componentDidMount() 内で登録したコールバックと全く同じインスタンスを componentWillUnmount() 内で開放できる。

まとめ

bind()は常に同じインスタンスを返すわけではない。

補足

React.createClass の↓部分 でAutoBindロジックが組み込まれている。ES6のクラス記法を用いると、もちろんこれは効かないので、関数を呼び出す箇所で明示的にbind(this)する必要があった。

github.com

が、リスナーの登録/解除 のように、同じコールバックであることを前提とする場合には、bind(this) が常に新しいインスタンスを返却することを意識しておく必要がある。