javascriptのthis参照を✅

どうしても解決しなかったエラーを二文字だけで解決してくださった普久原さんに感謝している23期スバルです。

今日は1週間に一度だけ訪れるチートデイです。

チートデイは外食と銭湯に行き、運動による肉体疲労と勉強による精神疲労を一気に癒します。

普段は沼(鶏胸肉 + 米 + 乾燥椎茸 + 乾燥わかめ + カレー粉)生活をしているだけに、楽しみな日なのですが、その人MVPリリース日が被ってしまいました。

疲れを取ったのも束の間、MVPリリースに向けて自分に鞭打って最終点検を行いました。

なんとかMVPリリースができてよかったです。

とはいえ、これからが本番なわけで。

皆さんの声を元に12月11日リリースに向けてPDCA回して良いポートフォリオにしていきたいです。

さて話は学習内容に戻り、今回はjsvascriptのthisをチェックしていきたいと思います。

thisとは

thisは読み取り専用のグローバル変数のようなものでどこにでも書けます。 加えて、thisの参照先(評価結果)は条件によって異なります。

thisの参照先は主に次の条件によって変化するそう。

  • 実行コンテキストにおけるthis
  • コンストラクタにおけるthis
  • 関数とメソッドにおけるthis
  • Arrow Functionにおけるthis

ひとつづつ見ていきたい。

実行コンテキストにおけるthis

トップレベル(もっとも外側のスコープ)にあるthisは、実行コンテキストによって値が異なるそう。

実行コンテキストにおいてthisの違いは認識しづらいため、トップレベルでthisを使うと混乱を生むことになりる。 そのため、コードのトップレベルにおいてはthisを使うことはバッドプラクティスだそう。

それぞれの実行コンテキストにおける動作は以下の通り。

スクリプトにおけるthis

実行コンテキストが"Script"である場合、トップレベルのスコープに書かれたthisはグローバルオブジェクトを参照する。

グローバルオブジェクトは実行環境ごとに異なる。

ブラウザのグローバルオブジェクト: windowオブジェクト

Node.jsのグローバルオブジェクト: globalオブジェクト

ブラウザでは、script要素のtype属性を指定していない場合は、実行コンテキストが"Script"として実行されます。 このscript要素の直下に書いたthisはグローバルオブジェクトであるwindowオブジェクトとなる。

<script>
# 実行コンテキストは"Script"
console.log(this); // => window
</script>

モジュールにおけるthis

実行コンテキストが"Module"である場合、そのトップレベルのスコープに書かれたthisは常にundefinedとなる。

これは、Moduleの段階だとまだ参照する関数が定義されていないためである。

また、ブラウザで、script要素にtype="module"属性がついた場合は、実行コンテキストが"Module"として実行される。 このscript要素の直下に書いたthisundefinedとなる。

<script type="module">
# 実行コンテキストは"Module"
console.log(this); // => undefined
</script>

このように、トップレベルのスコープのthisは実行コンテキストによってundefinedとなる場合がある。

単純にグローバルオブジェクトを参照したい場合は、thisではなくglobalThisを使うそう。 

関数とメソッドにおけるthis

関数を定義する方法は下記のようなものがある。

  • functionキーワードによる関数宣言と関数式
  • Arrow Functionなど

thisが参照先を決めるルールは、Arrow Functionとそれ以外の関数定義の方法で異なるため、そもそもの関数の種類を復習する必要がある。

関数の種類

関数を定義する方法は以下の三つがある。

# functionキーワードからはじめる関数宣言
function fn1() {}

#functionを式として扱う関数式
const fn2 = function() {};

# Arrow Functionを使った関数式
const fn3 = () => {};

それぞれ定義した関数は関数名()と書くことで呼び出す事ができる。

# 関数宣言
function fn() {}
# 関数呼び出し
fn();

メソッドの種類

JavaScriptではオブジェクトのプロパティが関数である場合にそれをメソッドと呼ぶ。

メソッドと関数には以下のような違いがある。

  • メソッドも含めたものを関数と言う
  • 関数宣言などとプロパティである関数を区別する場合にメソッドと呼ぶ

メソッドを定義する場合には、オブジェクトのプロパティに関数式を定義するだけ。

const obj = {
    # `function`キーワードを使ったメソッド
    method1: function() {
    },
    # Arrow Functionを使ったメソッド
    method2: () => {
    }
};

これに加えてメソッドには短縮記法があり、

オブジェクトリテラルの中で メソッド名(){ /*メソッドの処理*/ }と書くことで、メソッドを定義できる。

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

これらのメソッドは、オブジェクト名.メソッド名()と書くことで呼び出す事ができる。

const obj = {
    # メソッドの定義
    method() {
    }
};
# メソッド呼び出し
obj.method();

関数宣言や関数式におけるthis

まずは、関数宣言や関数式の場合。

次の例では、関数宣言で関数fn1と関数式で関数fn2を定義し、それぞれの関数内でthisを返している。

定義したそれぞれの関数をfn1()fn2()のようにただの関数として呼び出しているが、このとき、ベースオブジェクトはないことから、thisundefinedとなることが確認できる。

"use strict";
function fn1() {
    return this;
}
const fn2 = function() {
    return this;
};
# 関数の中の`this`が参照する値は呼び出し方によって決まる
# `fn1`と`fn2`どちらもただの関数として呼び出している
# メソッドとして呼び出していないためベースオブジェクトはない
# ベースオブジェクトがない場合、`this`は`undefined`となる
console.log(fn1()); // => undefined
console.log(fn2()); // => undefined

これは、関数の中に関数を定義して呼び出す場合も同じ挙動をする。

"use strict";
function outer() {
    console.log(this); // => undefined
    function inner() {
        console.log(this); // => undefined
    }
    # `inner`関数呼び出しのベースオブジェクトはない
    inner();
}
# `outer`関数呼び出しのベースオブジェクトはない
outer();

メソッド呼び出しにおけるthis

メソッドの場合は、JavaScriptではオブジェクトのプロパティとして指定される関数のことをメソッドと呼ぶため、そのメソッドが何かしらのオブジェクトに所属することになっている。

次の例ではmethod1method2はそれぞれメソッドとして呼び出されている。 このとき、それぞれのベースオブジェクトはobjとなり、thisobjとなる。

const obj = {
    # 関数式をプロパティの値にしたメソッド
    method1: function() {
        return this;
    },
    # 短縮記法で定義したメソッド
    method2() {
        return this;
    }
};
# メソッド呼び出しの場合、それぞれの`this`はベースオブジェクト(`obj`)を参照している。
# メソッド呼び出しの`.`の左にあるオブジェクトがベースオブジェクト
console.log(obj.method1()); // => obj
console.log(obj.method2()); // => obj

これを利用すれば、メソッドの中から同じオブジェクトに所属する別のプロパティをthisで参照する事ができる。

const alcohol = {
    alcoholName: "beer",
    sayName: function() {
        # `alcohol.fullName`と書いているのと同じ
        return this.fullName;
    }
};
#`alcohol.fullName`を出力する
console.log(alcohol.sayName()); // => "beer"

Run

このようにメソッドが所属するオブジェクトのプロパティを、オブジェクト名.プロパティ名の代わりにthis.プロパティ名で参照する事ができる。

オブジェクトは何重にもネストできるが、thisはベースオブジェクトを参照するというルールは同じになっている。

次のコードは、ネストしたオブジェクトにおいてメソッド内のthisがベースオブジェクトであるobj3を参照していることが読み取れる。

このときのベースオブジェクトはドットでつないだ一番左のobj1ではなく、メソッドから見てひとつ左のobj3となっている。

const obj1 = {
    obj2: {
        obj3: {
            method() {
                return this;
            }
        }
    }
};
# `obj1.obj2.obj3.method`メソッドの`this`は`obj3`を参照
console.log(obj1.obj2.obj3.method() === obj1.obj2.obj3); // => true

感想

今日はしんどい1日だった、、

ポモドーロテクニックを教えていくださった井上さん、タイムクラウドのにしこさんに感謝!

次回は12月18日。楽しみ!

関数とthis