GitBookjs-primer関数とthisこの章ではthisという特殊な動作をするキーワードについて見ていきます。基本的にはメソッドの中で利用しますが、thisは読み取り専用のグローバル変数のようなものでどこにでも書けます。加えて、thisの参照先(評価結果)は条件によって異なります。thisの参照先は主に次の条件によって変化します。Functionにおけるthisコンストラクタにおけるthisは、次の章である「クラス」で扱います。この章ではさまざまな条件でのthisについて扱いますが、thisが実際に使われるのはメソッドにおいてです。そのため、あらゆる条件下でのthisの動きを覚える必要はありません。Functionとの関係を見ていきます。実際にどのような状況で問題が発生するかを知り、thisの動きを予測可能にするにはどのようにするかを見ていきます。実行コンテキストとthis最初に「JavaScriptとは」の章において、JavaScriptには実行コンテキストとして"Script"と"Module"があるという話をしました。どの実行コンテキストでJavaScriptのコードを評価するかは、実行環境によってやり方が異なります。この章では、ブラウザのscript要素とtype属性を使い、それぞれの実行コンテキストを明示しながらthisの動きを見ていきます。トップレベル(もっとも外側のスコープ)にあるthisは、実行コンテキストによって値が異なります。実行コンテキストの違いは意識しにくい部分であり、トップレベルでthisを使うと混乱を生むことになります。そのため、コードのトップレベルにおいてはthisを使うべきではありませんが、それぞれの実行コンテキストにおける動作を紹介します。スクリプトにおけるthis実行コンテキストが"Script"である場合、トップレベルのスコープに書かれたthisはグローバルオブジェクトを参照します。グローバルオブジェクトには、実行環境ごとに異なるものが定義されています。ブラウザならwindowオブジェクト、Node.jsならglobalオブジェクトとなります。ブラウザでは、script要素のtype属性を指定していない場合は、実行コンテキストが"Script"として実行されます。このscript要素の直下に書いたthisはグローバルオブジェクトであるwindowオブジェクトとなります。window</script>モジュールにおけるthis実行コンテキストが"Module"である場合、そのトップレベルのスコープに書かれたthisは常にundefinedとなります。ブラウザで、script要素にtype="module"属性がついた場合は、実行コンテキストが"Module"として実行されます。このscript要素の直下に書いたthisはundefinedとなります。undefined</script>このように、トップレベルのスコープのthisは実行コンテキストによってundefinedとなる場合があります。単純にグローバルオブジェクトを参照したい場合は、thisではなくwindowなどのグローバルオブジェクトを直接参照したほうがよいです。Functionなどがあります。Functionとそれ以外の関数定義の方法で異なります。そのため、まずは関数定義の種類について振り返ってから、それぞれのthisについて見ていきます。「関数と宣言」の章で詳しく紹介していますが、関数の定義方法と呼び出し方について改めて振り返ってみましょう。関数を定義する場合には、次の3つの方法を利用します。{};それぞれ定義した関数は関数名()と書くことで呼び出せます。関数呼び出しfn();メソッドの種類JavaScriptではオブジェクトのプロパティが関数である場合にそれをメソッドと呼びます。一般的にはメソッドも含めたものを関数と言い、関数宣言などとプロパティである関数を区別する場合にメソッドと呼びます。メソッドを定義する場合には、オブジェクトのプロパティに関数式を定義するだけです。}};これに加えてメソッドには短縮記法があります。}と書くことで、メソッドを定義できます。}};これらのメソッドは、オブジェクト名.メソッド名()と書くことで呼び出せます。メソッド呼び出しobj.method();関数定義とメソッドの定義についてまとめると、次のようになります。Functionの関数定義とそれ以外(functionキーワードやメソッドの短縮記法)の関数定義で異なります。Function以外の関数やメソッドにおけるthisを見ていきます。Function以外の関数(メソッドも含む)におけるthisは、実行時に決まる値となります。言い方を変えるとthisは関数に渡される暗黙的な引数のようなもので、その渡される値は関数を実行するときに決まります。次のコードは疑似的なものです。関数の中に書かれたthisは、関数の呼び出し元から暗黙的に渡される値を参照することになります。Function以外の関数やメソッドで共通した仕組みとなります。Functionで定義した関数やメソッドはこのルールとは別の仕組みとなります。引数);関数におけるthisの基本的な参照先(暗黙的に関数に渡すthisの値)はベースオブジェクトとなります。ベースオブジェクトとは「メソッドを呼ぶ際に、そのメソッドのドット演算子またはブラケット演算子のひとつ左にあるオブジェクト」のことを言います。ベースオブジェクトがない場合のthisはundefinedとなります。fn()のように関数を呼び出したとき、このfn関数呼び出しのベースオブジェクトはないため、thisはundefinedとなります。obj.method()のようにメソッドを呼び出したとき、このobj.methodメソッド呼び出しのベースオブジェクトはobjオブジェクトとなり、thisはobjとなります。ドット演算子、ブラケット演算子どちらも結果は同じobj1.obj2.method();obj1["obj2"]["method"]();thisは関数の定義ではなく呼び出し方で参照する値が異なります。これは、後述する「thisが問題となるパターン」で詳しく紹介します。Function以外の関数では、関数の定義だけを見てthisの値が何かということは決定できない点に注意が必要です。関数宣言や関数式におけるthisまずは、関数宣言や関数式の場合を見ていきます。次の例では、関数宣言で関数fn1と関数式で関数fn2を定義し、それぞれの関数内でthisを返します。定義したそれぞれの関数をfn1()とfn2()のようにただの関数として呼び出しています。このとき、ベースオブジェクトはないため、thisはundefinedとなります。undefinedこれは、関数の中に関数を定義して呼び出す場合も同じです。modeを明示しています。modeではない状況でthisがundefinedの場合は、thisがグローバルオブジェクトを参照するように変換される問題があるためです。modeは、このような意図しにくい動作を防止するために導入されています。modeのメソッド以外の関数におけるthisはundefinedとなるため使い道がありません。そのため、メソッド以外でthisを使う必要はありません。メソッド呼び出しにおけるthis次に、メソッドの場合を見ていきます。メソッドの場合は、そのメソッドが何かしらのオブジェクトに所属しています。JavaScriptではオブジェクトのプロパティとして指定される関数のことをメソッドと呼ぶためです。次の例ではmethod1とmethod2はそれぞれメソッドとして呼び出されています。このとき、それぞれのベースオブジェクトはobjとなり、thisはobjとなります。objこれを利用すれば、メソッドの中から同じオブジェクトに所属する別のプロパティをthisで参照できます。Eich"このようにメソッドが所属するオブジェクトのプロパティを、オブジェクト名.プロパティ名の代わりにthis.プロパティ名で参照できます。オブジェクトは何重にもネストできますが、thisはベースオブジェクトを参照するというルールは同じです。次のコードを見てみると、ネストしたオブジェクトにおいてメソッド内のthisがベースオブジェクトであるobj3を参照していることがわかります。このときのベースオブジェクトはドットでつないだ一番左のobj1ではなく、メソッドから見てひとつ左のobj3となります。truethisが問題となるパターンthisはその関数(メソッドも含む)呼び出しのベースオブジェクトを参照することがわかりました。thisは所属するオブジェクトを直接書く代わりとして利用できますが、一方thisにはいろいろな問題があります。この問題の原因はthisがどの値を参照するかは関数の呼び出し時に決まるという性質に由来します。このthisの性質が問題となるパターンの代表的な2つの例とそれぞれの対策について見ていきます。thisを含むメソッドを変数に代入した場合JavaScriptではメソッドとして定義したものが、後からただの関数として呼び出されることがあります。メソッドは関数を値に持つプロパティのことで、プロパティは変数に代入し直すことができるためです。そのため、メソッドとして定義した関数も、別の変数に代入してただの関数として呼び出されることがあります。この場合には、メソッドとして定義した関数であっても、実行時にはただの関数であるためベースオブジェクトが変わっています。これはthisが定義した時点ではなく実行したときに決まるという性質そのものです。具体的に、thisが実行時に変わる例を見ていきます。次の例では、person.sayNameメソッドを変数sayに代入してから実行しています。このときのsay関数(sayNameメソッドを参照)のベースオブジェクトはありません。そのため、thisはundefinedとなり、undefined.fullNameは参照できずに例外を投げます。undefined結果的には、次のようなコードが実行されているのと同じです。次のコードでは、undefined.fullNameを参照しようとして例外が発生しています。Function以外の関数において、thisは定義したときではなく実行したときに決定されます。そのため、関数にthisを含んでいる場合、その関数は意図した呼ばれ方がされないと間違った結果が発生するという問題があります。この問題の対処法としては大きく分けて2つあります。1つはメソッドとして定義されている関数はメソッドとして呼ぶということです。メソッドをわざわざただの関数として呼ばなければそもそもこの問題は発生しません。1つは、thisの値を指定して関数を呼べるメソッドで関数を実行する方法です。call、apply、bindメソッド関数やメソッドのthisを明示的に指定して関数を実行する方法もあります。Function(関数オブジェクト)にはcall、apply、bindといった明示的にthisを指定して関数を実行するメソッドが用意されています。callメソッドは第一引数にthisとしたい値を指定し、残りの引数には呼び出す関数の引数を指定します。暗黙的に渡されるthisの値を明示的に渡せるメソッドと言えます。...関数の引数);次の例ではthisにpersonオブジェクトを指定した状態でsay関数を呼び出しています。callメソッドの第二引数で指定した値が、say関数の仮引数messageに入ります。undefinedapplyメソッドは第一引数にthisとする値を指定し、第二引数に関数の引数を配列として渡します。関数の引数2]);次の例ではthisにpersonオブジェクトを指定した状態でsay関数を呼び出しています。applyメソッドの第二引数で指定した配列は、自動的に展開されてsay関数の仮引数messageに入ります。undefinedcallメソッドとapplyメソッドの違いは、関数の引数への値の渡し方が異なるだけです。どちらのメソッドもthisの値が不要な場合はnullを渡すのが一般的です。3最後にbindメソッドについてです。名前のとおりthisの値を束縛(bind)した新しい関数を作成します。次の例ではthisをpersonオブジェクトに束縛したsay関数をラップした関数を作っています。bindメソッドの第二引数以降に値を渡すことで、束縛した関数の引数も束縛できます。Eich!"このbindメソッドをただの関数で表現すると次のように書けます。bindはthisや引数を束縛した関数を作るメソッドだということがわかります。Eich!"このようにcall、apply、bindメソッドを使うことでthisを明示的に指定した状態で関数を呼び出せます。毎回関数を呼び出すたびにこれらのメソッドを使うのは、関数を呼び出すための関数が必要になってしまい手間がかかります。そのため、基本的には「メソッドとして定義されている関数はメソッドとして呼ぶこと」でこの問題を回避するほうがよいでしょう。その中で、どうしてもthisを固定したい場合にはcall、apply、bindメソッドを利用します。コールバック関数とthisコールバック関数の中でthisを参照すると問題となる場合があります。この問題は、メソッドの中でArray#mapメソッドなどのコールバック関数を扱う場合に発生しやすいです。具体的に、コールバック関数におけるthisが問題となっている例を見てみましょう。次のコードではprefixArrayメソッドの中でArray#mapメソッドを使っています。このとき、Array#mapメソッドのコールバック関数の中で、Prefixerオブジェクトを参照するつもりでthisを参照しています。このコールバック関数におけるthisはundefinedとなり、undefined.prefixは参照できないためTypeErrorの例外が発生します。undefinedなぜコールバック関数の中のthisがundefinedとなるのかを見ていきます。Array#mapメソッドにはコールバック関数として、その場で定義した匿名関数を渡していることに注目してください。...このとき、Array#mapメソッドに渡しているコールバック関数はcallback()のようにただの関数として呼び出されます。コールバック関数として呼び出すとき、この関数にはベースオブジェクトはありません。そのためcallback関数のthisはundefinedとなります。先ほどの例では匿名関数をコールバック関数として直接メソッドに渡していますが、一度callback変数に入れてから渡しても結果は同じです。thisを一時変数へ代入するコールバック関数内でのthisの参照先が変わる問題への対処法として、thisを別の変数に代入し、そのthisの参照先を保持するという方法があります。thisは関数の呼び出し元で変化し、その参照先は呼び出し元におけるベースオブジェクトです。prefixArrayメソッドの呼び出しにおいては、thisはPrefixerオブジェクトです。コールバック関数は改めて関数として呼び出されるためthisがundefinedとなってしまうのが問題でした。そのため、最初のprefixArrayメソッド呼び出しにおけるthisの参照先を一時変数として保存することでこの問題を回避できます。次のコードでは、prefixArrayメソッドのthisをthat変数に保持しています。コールバック関数からはthisの代わりにthat変数を参照することで、コールバック関数からもprefixArrayメソッド呼び出しと同じthisを参照できます。"pre-c"]もちろんFunction#callメソッドなどで明示的にthisを渡して関数を呼び出すこともできます。Array#mapメソッドなどはthisとなる値を引数として渡せる仕組みを持っています。そのため、次のように第二引数にthisとなる値を渡すことでも解決できます。"pre-c"]しかし、これらの解決方法はコールバック関数においてthisが変わることを意識して書く必要があります。そもそもメソッド呼び出しとその中でのコールバック関数におけるthisが変わってしまうのが問題でした。Functionが導入されました。Functionでコールバック関数を扱う通常の関数やメソッドは呼び出し時に暗黙的にthisの値を受け取り、関数内のthisはその値を参照します。Functionはこの暗黙的なthisの値を受け取りません。Function内のthisは、スコープチェーンの仕組みと同様に外側の関数(この場合はprefixArrayメソッド)を探索します。Functionで定義したコールバック関数は呼び出し方には関係なく、常に外側の関数のthisをそのまま利用します。Functionを使うことで、先ほどのコードは次のように書けます。Functionでのコールバック関数におけるthisは簡潔です。Functionを使うのがもっとも簡潔です。Functionとthisの関係についてより詳しく見ていきます。Functionで定義された関数やメソッドにおけるthisがどの値を参照するかは関数の定義時(静的)に決まります。Functionではない関数においては、thisは呼び出し元に依存するため関数の実行時(動的)に決まります。Functionはthisを暗黙的な引数として受けつけないということです。Function内にはthisが定義されていません。このときのthisは外側のスコープ(関数)のthisを参照します。これは、変数におけるスコープチェーンの仕組みと同様で、そのスコープにthisが定義されていない場合には外側のスコープを探索します。Function内のthisの参照で、常に外側のスコープ(関数)へとthisの定義を探索しに行きます(詳細はスコープチェーンを参照)。thisはECMAScriptのキーワードであるため、ユーザーはthisという変数を定義できません。thisこれにより、通常の変数のようにthisがどの値を参照するかは静的(定義時)に決定されます(詳細は静的スコープを参照)。Function自身の外側のスコープに定義されたもっとも近い関数のthisの値」となります。Functionにおけるthisの動きを見ていきましょう。Functionを見ていきます。Functionの中のthisをコンソールに出力しています。このとき、fnの外側には関数がないため、「自身より外側のスコープに定義されたもっとも近い関数」の条件にあてはまるものはありません。このときのthisはトップレベルに書かれたthisと同じ値になります。trueトップレベルに書かれたthisの値は実行コンテキストによって異なることを紹介しました。thisの値は、実行コンテキストが"Script"ならばグローバルオブジェクトとなり、"Module"ならばundefinedとなります。Functionを包むように通常の関数が定義されている場合はどうでしょうか。Functionにおけるthisは「自身の外側のスコープにあるもっとも近い関数のthisの値」となるのは同じです。Functionにおけるthisはouter関数でthisを参照した場合と同じ値になります。Functionをより活用できるパターンです。functionキーワードでコールバック関数を定義すると、thisの値はコールバック関数の呼ばれ方を意識する必要があります。functionキーワードで定義した関数におけるthisは呼び出し方によって変わるためです。コールバック関数側から見ると、どのように呼ばれるかによって変わってしまうthisを使うことはできません。そのため、コールバック関数の外側のスコープでthisを一時変数に代入し、それを使うという回避方法を取っていました。Functionでコールバック関数を定義した場合は、1つ外側の関数のthisを参照します。Functionで定義したコールバック関数におけるthisは呼び出し方によって変化しません。そのため、thisを一時変数に代入するなどの回避方法は必要ありません。Functionにおけるthisは呼び出し方の影響を受けません。コールバック関数がどのように呼ばれるかという実装についてを考えることなくthisを扱えます。Functionで定義した関数ではcall、apply、bindを使ったthisの指定は単に無視されます。Functionはthisを持てないためです。Functionで定義した関数に対してcallでthisを指定しても、thisの参照先が代わっていないことがわかります。同様にapplyやbindメソッドを使った場合もthisの参照先は変わりません。グローバルオブジェクト最初に述べたようにfunctionキーワードで定義した関数では呼び出し時に、ベースオブジェクトがthisの値として暗黙的な引数のように渡されます。Functionの関数は呼び出し時にthisを受け取らず、thisの参照先は定義時に静的に決定されます。Functionのthisが参照する「自身の外側のスコープにあるもっとも近い関数のthisの値」はcallメソッドで変更できます。"THAT"まとめthisは状況によって異なる値を参照する性質を持ったキーワードであることを紹介しました。そのthisの評価結果をまとめると次の表のようになります。obj*はどの場合でもthisの評価結果に影響しないということを示しています。JavaScriptというサイトで確認できます。thisはオブジェクト指向プログラミングの文脈でJavaScriptに導入されました。modeなどによって結果が異なり、混乱の元となります。そのため、メソッドではない通常の関数においてはthisを使うべきではありません。1また、メソッドにおいてもthisは呼び出し方によって異なる値となり、それにより発生する問題と対処法について紹介しました。Functionを使うことでわかりやすく解決できます。Functionで定義した関数はthisを持たないという性質があります。Wirfs-Brock‏氏もただの関数においてはthisを使うべきではないと述べている。https://twitter.com/awbjs/status/938272440085446657;。