Code Fix

中級

メソッドを変数に代入・受け渡しするとthisが失われる

オブジェクトのメソッドを変数に代入したり、別の関数に渡して呼び出したりすると、呼び出し時にレシーバーの情報が失われ、thisがオブジェクトを指さなくなります。

なぜエラーが出ないのか

出力: undefined
(エラーなし)
JavaScriptは何も報告しません。文法として正しいためです
出力: undefined
実際の挙動 — 期待した結果と食い違っている箇所
見つけ方
エラーが出ないので、出力を目で確かめるか、コードを目で追うしかありません。この種の誤りが最も発見が遅れます

このエラーが出る典型パターン

パターン1

 1  const counter = { count: 5, getCount: function() { return this.count; } };
 2  const getCount = counter.getCount.bind(        );
                                               ^
 3  console.log(getCount());
出力: undefined期待: 5 (エラーにはなりません)

bind()に何も渡さないと、切り離されたgetCountを呼んでもthisはcounterを指しません。bind(counter)でthisを固定する必要があります。

直し方: (空) を counter にします。

この問題を解いてみる →

広告
広告スロット(未設定)

パターン2

 1  const logger = { prefix: "[LOG]", write(msg) { return this.prefix + " " + msg; } };
 2  function run(fn) { return fn("hi"); }
 3  console.log(run(logger.write             ));
                                ^
出力: undefined hi期待: [LOG] hi (エラーにはなりません)

logger.writeを関数として渡すと、run内でfn("hi")と呼ばれた時点でレシーバーの情報が失われます。bindでthisをloggerに固定してから渡す必要があります。

直し方: logger.write を logger.write.bind(logger) にします。

この問題を解いてみる →

パターン3

 1  const logger = { prefix: "[LOG]", write(msg) { return this.prefix + " " + msg; } };
 2  const { write } = logger;
 3  console.log(write("hi")             );
                            ^
出力: undefined hi期待: [LOG] hi (エラーにはなりません)

分割代入で取り出したwriteはloggerとの結びつきを失っています。call(logger, ...)で明示的にthisを指定する必要があります。

直し方: write("hi") を write.call(logger, "hi") にします。

この問題を解いてみる →

パターン4

 1  const timer = { seconds: 0, tick: function() { this.seconds++; return this.seconds; } };
 2  const tick = timer.tick.bind(     );
                                   ^
 3  console.log(tick());
出力: NaN期待: 1 (エラーにはなりません)

bind()に何も渡さないと、切り離されたtickを呼んでもthisはtimerを指しません。

直し方: (空) を timer にします。

この問題を解いてみる →

パターン5

 1  const wallet = { balance: 100, show() { return "$" + this.balance; } };
 2  function callLater(fn) { return fn(); }
 3  console.log(callLater(wallet.show             ));
                                      ^
出力: $undefined期待: $100 (エラーにはなりません)

wallet.showを関数として渡すと、callLater内で呼ばれた時点でレシーバーの情報が失われます。

直し方: wallet.show を wallet.show.bind(wallet) にします。

この問題を解いてみる →

よくある誤解

「メソッドはどこから呼んでも同じオブジェクトに属する」という思い込みは誤りです。JavaScriptのthisは定義位置ではなく呼び出し方によって決まるため、切り離して渡した瞬間に本来のレシーバーとの結びつきは失われます。

実務での勘所

thisが失われるのは、JavaScriptのthisが関数を定義した場所ではなく、呼び出した瞬間の「ドットの左側に何があったか」で決まる仕組みだからです。obj.method()ならthisはobjになりますが、const f = obj.method; f()のように呼ぶと、呼び出し時にドットの左側に何も無いため、thisはundefined(厳格モード)かグローバルオブジェクトになります。定石の対処は3つあり、アロー関数(定義時の外側のthisをそのまま使う)、.bind(obj)でthisを固定したコピーを作る、あるいは呼び出し側でアロー関数に包んで渡す、のいずれかです。

演習をはじめる

関連するエラー

広告
広告スロット(未設定)