BETA

JavaScript thisの挙動を学習する

投稿日:2019-02-18
最終更新:2019-02-18

JavaScriptを使った開発をする上で知っておくべき知識は複数ありますが、
その中で挙動がややこしいthisを学習していきます。

※この記事は参考URLの内容を元にして自分の言葉で整理することでの
アウトプットを目的としています。
内容に不安がある方は参考URLよりご確認ください。

thisとは

主に関数の中で使用される特別な値です。
関数の外でも利用することができ、参照先は条件によって異なります。

この条件によって参照先が変わることで意図しない値(undefined)になり、
コードを書いている時に詰まる方も多いのではないのでしょうか。

条件

以下ではよく使用される、挙動がややこしいものをピックアップしています。

  • コンストラクタで使用する場合
  • 関数とメソッドで使用する場合
  • コールバック関数で使用する場合
  • アロー関数で使用する場合

またstrict modeとそうでない場合でもthisの値が変わってきます。
(undefinedかグローバルオブジェクトか)
以下はstrict modeでの説明になりますので注意してください。

複数ありますが1つずつ追っていきます・・・

コンストラクタで使用する場合

コンストラクタ??と思われる方もいると思います。
コンストラクタについてはclass構文の記事を書いた時に詳細を載せます。
(this自体の挙動はあまり難しくないのですがコンストラクなどの意味を知らずに
thisのみの挙動を知ってもあまり意味がないと思いますので)

関数とメソッドで使用する場合

関数でthisが参照先を決めるルールは
アロー関数とそれ以外の関数(関数宣言、関数式、メソッド)
で異なります。
このルールは事前に関数についての前提知識がないと混乱するのでまずは整理していきます。

関数定義の種類と実行方法

関数定義の種類

関数定義の種類は以下のものがあります。

  • functonキーワードを使った関数宣言
  • 式として扱い、変数に代入する関数式
  • ES2015から追加されたアロー関数(thisの参照先を決めるルールが上の2つとは違う)

実行方法

定義した関数は関数名()で呼び出すことがきます。

//それぞれの定義  
function sengen() {};  //関数宣言  
const kansuuSiki = function () {}; // 関数式  
const arrowFuncton = () => {}; //アロー関数  

//実行方法  
sengen();  
kansuuSiki();  
arrowFunction();

メソッド

メソッドとはJavaScriptではオブジェクトのプロパティが関数の場合の呼び方です。
(一般的にはメソッドも関数ですが、関数宣言とプロパティの関数を区別する場合にメソッドと呼びます)
メソッドの定義方法はオブジェクトのプロパティに関数を定義するだけです。
実行方法はオブジェクト名.メソッド名()で呼び出せます。

//メソッドの定義  
const object = {  
    method: function() { // プロパティのキーがmethodで値がfunction  
    },  
    method2: () =>{ //アロー関数での関数定義  
    }  
};  

//実行方法  
object.method();  
object.method2();

※こちらはメソッドの短縮記法です。初見だと混乱して理解が追いかけれなくなるので
参考に載せておきます

const object = {  
    method() { // メソッドの短縮記法で定義したメソッド  
    }  
};

関数におけるthisの参照先が決まるルール

最初にまとめておくと
関数(メソッドを含む)で使用されるthisは関数実行時に決まり
基本的な参照先はベースオブジェクトの値になるということです。

※上記にもありますがアロー関数はthisの参照先を決めるルールが違います

ベースオブジェクトとは

ベースオブジェクトとは「メソッドを呼ぶ際に、そのメソッドのドット演算子またはブラケット演算子のひとつ左にあるオブジェクト」のことを言います。 ベースオブジェクトがない場合のthisはundefinedとなります。
引用元 js-primer 関数とthisより

コードで例を見ていきます

// `kansuu`関数はメソッドではないのでベースオブジェクトはない  
kansuu();  
// `obj.method`メソッドのベースオブジェクトは`obj`  
obj.method();  
// `obj1.obj2.method`メソッドのベースオブジェクトは`obj2`  
// ドット演算子、ブラケット演算子どちらも結果は同じ  
obj1.obj2.method();  
obj1["obj2"]["method"]();

上記のコードで
関数宣言や関数式で定義したkansuu関数をkansuu()で呼び出してもベースオブジェクトがないため
thisはundefinedになります。
メソッドであるobj.methodを呼び出す場合はオブジェクトに属している(だからメソッドと呼ぶ)のでベースオブジェクトがあり、thisはobjを参照します。

関数実行時にthisの参照先が決まるというルールを知っていれば
メソッドを変数に代入した場合の挙動がわかると思います。

const say = obj.method; //メソッドの参照で実行はしていないので注意  
say();

変数sayにobj.methodメソッドを代入後にsay()で呼び出しています。
この時の呼び出しではベースオブジェクトがないのでthisはundefinedとなります。
これが意図せぬ値になってエラーになる原因のひとつです。
繰り返しますが関数(関数宣言・関数式・メソッド)におけるthisの
参照先は実行時に決まるということです。

このような問題を避けるにはメソッドとして定義されている関数はメソッドのまま呼ぶということです。そうすればベースオブジェクトは変わらず参照先が変わることはなくなります。
ですがどうしてもメソッドをただの関数として呼び出して実行しないといけない時がくるかもしれません。
その場合はcall、apply、bindメソッドを使いthisの値を指定して関数を実行します。

この3つのメソッドの使い方は参考URLで見た方が確実ですのでそちらを参照してください。

コールバック関数で使用する場合

ここまでは割と分かりやすいかと思いますがこのコールバックが絡んでくると
多少複雑になります。thisの挙動というよりはコードの扱いに慣れていないと
処理を理解するのが難しく感じます。感じました。
しっかりと理解して腹落ちしたい場合はコールバックについての知識も深めておいた方が
いいと思います。

以下のコードは参考URLから引用しています。
Array.prototype.mapメソッドにコールバック関数を匿名関数で渡しています。
その中で引数stringにthisの参照先の値を文字列連結をするという処理をしています。
そしてメソッド呼び出しでPrefixer.prefixArrayを実行しています。
上記までの説明だとベースオブジェクトがあるのでthisはPrefixerを参照すると思いますが
今回の例ではコールバック関数内でthisを利用しています。
アロー関数以外の関数は実行時にthisの参照先が決まるというルールがあります。
そしてコールバック関数の呼び出され方はただの関数呼び出しです。
なのでコールバック関数はベースオブジェクトを持っていないのでundefinedになります。

"use strict";  
const Prefixer = {  
    prefix: "pre",  
    /**  
     * `strings`配列の各要素にprefixをつける  
     */  
    prefixArray(strings) {  
        return strings.map(function(string) {  
            // コールバック関数における`this`は`undefined`となる(strict mode)  
            // そのため`this.prefix`は`undefined.prefix`となり例外が発生する  
            return this.prefix + "-" + string;  
        });  
    }  
};  
// `prefixArray`メソッドにおける`this`は`Prefixer`  
Prefixer.prefixArray(["a", "b", "c"]); // => TypeError: Cannot read property 'prefix' of undefined

ではコールバック関数内でthisは使用できない?と思われるかもしれませんが
対処法があります。

  • thisを一時変数に代入する
  • アロー関数を使用する

thisを一時変数に代入する

文字通りthisを変数に代入して利用します。

const that = this; //thatの他にもselfや_thisが使われる。指定ではない

thisの参照先をthatに代入することでコールバック関数内でも使用することができます。
しかし毎回コールバック関数でthisを使用する時に変数に代入する処理をするのは
いい解決方法ではありません。そこでthisの参照先を変えずにコールバック関数を定義
する方法としてアロー関数が追加されました。

アロー関数を使用する

アロー関数でコールバック関数を使用した場合は以下の挙動をします。

  • アロー関数で定義された関数やメソッドは定義時(静的)にthisの参照先が決まる
  • アロー関数は外側のスコープ(関数)のthisを参照する
  • 変数のスコープチェーンと仕組みが同じで、常にthisの参照先を外側のスコープへと探索をしに行く
  • アロー関数自身はcall、apply、bindの指定を無視する
  • アロー関数のthisが参照する「自身の外側のスコープにあるもっとも近い関数のthisの値」
    はcallメソッドで変更できる

まとめ

関数(関数宣言・関数式・メソッド)におけるthisの参照先は実行時に決まる。
ベースオブジェクトがthisの参照先になる。
コールバック関数内でthisを使用する際はアロー関数がとても有効
大分まとめが短くなってしまいましたが詳しい挙動を参考URLで確認してみてください。
thisの挙動を理解していなくてもコードを書いて機能を追加することはできてしまいます。
行を変えてconsole.logでthisの値を表示するといった時間をかけるといった手法です。
その場では解決できますが(解決していないかもしれない)次回同じ場面になった
時に同じ苦しみが襲ってきます。賽の河原です。
今回まとめてみてthisに関しての疑問が結構解消されました。引き続き学習していきます。

参考URL

関数とthis

技術ブログをはじめよう Qrunch(クランチ)は、プログラマの技術アプトプットに特化したブログサービスです
駆け出しエンジニアからエキスパートまで全ての方々のアウトプットを歓迎しております!
or 外部アカウントで 登録 / ログイン する
クランチについてもっと詳しく

この記事が掲載されているブログ

@tomorebの技術ブログ

よく一緒に読まれる記事

0件のコメント

ブログ開設 or ログイン してコメントを送ってみよう
目次をみる
技術ブログをはじめよう Qrunch(クランチ)は、プログラマの技術アプトプットに特化したブログサービスです
or 外部アカウントではじめる
10秒で技術ブログが作れます!