TypeScriptのthisについての忘備録
こんにちは!
スマレジ テックファームのMichiです!
TypeScriptのthis
は不思議な動きをすることで有名です。自分も定期的に調べては忘れてしまうので、今回は忘備録として残しておきます。
thisとは?
まず前提として、this
とは何かについて説明しておきます。
this
とは自分自身を表すオブジェクトです。次のコードを見てください。
class User { name: string; #age: number; constructor(name: string, age: number) { this.name = name; this.#age = age; } public isAdult() { return this.#age >= 20; } } const mike = new User("Mike", 26); console.log(mike.isAdult()); // 結果:true
上記のコードはconsole.log
でmike.isAdult()
の実行結果を出力しています。
isAdult()
メソッドの返り値はreturn this.#age >= 20;
です。このとき、this
は呼び出し元のオブジェクトになります。
具体的には、今回の例ではmike.isAdult()
という形でメソッドを読んでいるので、this
はmike
になります。mike
は#age = 26
というプロパティを持つUser
型のインスタンスなので、return this.#age >= 20;
の結果はtrue
となります。
よって、mike.isAdult()
の実行結果もtrue
となるわけです。
呼び出し方によってthisの値が変わる?
これだけならば簡単なのですが、そうはいかないのがTypeScriptのthis
がややこしいところです。
次のコードを見てください。
class User { name: string; #age: number; constructor(name: string, age: number) { this.name = name; this.#age = age; } public isAdult() { return this.#age >= 20; } } const mike = new User("Mike", 26); const isAdult = mike.isAdult; console.log(isAdult()); // ランタイムエラー: Cannot read private member from an object whose class did not declare it
先ほどとは違い、mike.isAdult
をいったんisAdult
に代入してから呼び出しています。
いったん代入しただけで、処理は変えていないので結果は変わらないと思えそうですが、実際はランタイムエラーが発生していまいます。
実は、メソッド記法(オブジェクト.メソッド名()
の形)を使わずに関数が呼び出された場合、その中でのthis
の値はundefined
になります。*1なぜなら、今回isAdult()
はグローバルスコープから呼び出されたので、参照するオブジェクトが存在しないからです。
this
の値がundefined
である以上、 return this.#age >= 20;
の結果は当然エラーです。よって、上記のコードを実行しても、ランタイムエラーとなるわけです。
このことからわかるように、「this
は関数の呼び出し方によって参照が変わる」という少し厄介な性質を持っています。今回のようなエラーを避けるために、this
を使うオブジェクトのメソッドは原則として、メソッド記法で呼んだ方がよいでしょう。
アロー関数はthisを持たない
ここまで書いてきて、一つ例外があります。それはアロー関数を使用した場合です。
次のコードを見てください。
class User { name: string; #age: number; constructor(name: string, age: number) { this.name = name; this.#age = age; } public isAdult = () => { return this.#age >= 20; }; } const mike = new User("Mike", 26); const isAdult = mike.isAdult; console.log(isAdult()); // 結果:true
isAdult
の定義にアロー関数を使用した以外は、先ほどのコードと同じです。よって、実行結果も同じくランタイムエラーとなりそうな気がします。
ところが、このコードは正しく動作します。これは、「アロー関数は自分自身のthis
を持たない」という特殊な性質によるものです。
自分自身のthis
を持たないならば、アロー関数はどこからthis
の参照を引っ張ってきているのでしょうか?
この場合、アロー関数はthis
を外側のスコープから引き継ぎます。上記の例において、isAdult
はUser
クラスのスコープに属しています。よって、このthis
は、User
から生成されたインスタンス(オブジェクト)となり、ランタイムエラーとはならないわけです。
アロー関数は通常の関数と異なり、呼び出し方によってthis
の参照が変化しません。アロー関数を定義した段階で、this
が何であるかは決まっているのです。
このことからも、普段から通常の関数よりもアロー関数を使うほうがよいといえます。
参考
プロを目指す人のためのTypeScript入門 安全なコードの書き方から高度な型の使い方まで (Software Design plus) | 鈴木 僚太 |本 | 通販 | Amazon
*1:JavaScriptのstrictモードがOFFの場合は、thisはグローバルオブジェクトとなります。TypeScriptでは、原則すべてstrictモードで実行されるので、undefinedになると覚えておいて問題ありません。