派遣で働くエンジニアのスキルアップを応援するサイト

PRODUCED BY RECRUIT

JavaScriptを学ぶ上でつまずきやすいポイントを理解するための連載【第1回】JS特有の「this」の使い方6つをおさえよう

JavaScriptは直観的でわかりやすく、最も開発者人口が多いメジャーな言語だと言われています。一方で、学習を進めていくと奥が深く、理解が難しい機能や概念もいくつかあります。この連載では、JavaScriptを学ぶ方がよくつまずくトピックをとりあげて解説します。

■本コラムの対象読者

  • 変数、条件分岐や繰り返しのようなプログラミングの基礎概念をすでに理解している方
  • JavaScriptにおける初学者向けの書籍等で最低限の知識を吸収した上で、より実践的なコードを書けるようになりたい方

JavaScriptの学習でつまずきがちな内容として、this キーワードは多くの方が挙げるトピックではないでしょうか。JavaScript以外の多くの言語においても、オブジェクト指向にまつわる機能としてthisやselfのような名称で存在します。しかし、JavaScriptにおけるthisは関数の単位で関係するため、少し複雑な動きをします。今回はこのthisについて6つの使い方を確認していきましょう。

■今回のまとめ
  • thisは「関数が呼び出されたとき」に、その所有者となっているオブジェクトを指し示すが(多言語共通)、 特にJavaScriptにおいては「呼び出されたときの状態」によって、動作が変化する →ポイント1〜4
  • thisを意図的に設定する方法もある →ポイント5,6
※6つのポイントの重要度を三段階にわけて★をつけました。学習の参考にしてください。

【筆者】佐藤正志さん
契約社員、客員研究員、フリーランスなどさまざまな立場で開発現場を経験。フロントエンド、バックエンド開発、チームリードやOJTの育成担当まで幅広くこなしてきた。現在プログラミングトレーニング事業を行うサークルアラウンド株式会社を立ち上げ、経営や現場開発を行いながらOJT経験をもとに後進の育成に励んでいる。ノウハウのコンテンツ化にも取り組んでおり、著書に『ステップアップJavaScript』(翔泳社)がある。

ポイント1:グローバルオブジェクトを指すthis(重要度★★)

ブラウザでは以下のような場合、thisによってグローバルオブジェクトのwindowが取得されます。

  • 関数に囲まれていないグローバルなスコープ
  • 呼び出しオブジェクトのない、シンプルな関数内(※)

※ use strict を指定した厳格モードの場合には、undefined が返ります

// 何も関数に囲まれていないグローバルスコープのthisはグローバルオブジェクトです
console.log(this === window); // => true

function globalTest() {
  console.log(this === window); // => true
}

// オブジェクトに所有されていないのでthisはwindowです
globalTest();

 

ポイント2:オブジェクトのメソッドのthis(重要度★★)

例として変数userを以下のようなObjectで作成したとしましょう。

const user = {
  firstName: "Taro",
  lastName: "Suzuki",
  getFullName: function () {
    return `${this.firstName} ${this.lastName}`;
  },
};

console.log(user.getFullName()); //=> Taro Suzuki

user.getFullName()の呼び出し時に、thisを介して呼び出し元オブジェクト(レシーバと呼ばれることもあります)のプロパティにアクセスできます。これを利用して、userのfirstNameとlastNameを連結するロジックが書けます。

user.firstName、user.lastNameが利用できるので、thisがuserと同一のオブジェクトであると想像すればthis.firstName と呼べることがわかりますね。

 

ポイント3:クラスにおけるthis(重要度★★)

多くの言語におけるthisの基本動作と言える使い方です。クラスについて理解のある人は、この説明は見知ったものになるはずです。
先のコードと同様のものをクラスで書きました。厳密な内部構造は違いますが、変数userのオブジェクトと同様の操作を変数user2に対して利用できます。

class User {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }

  getFullName() {
    return `${this.firstName} ${this.lastName}`;
  }
}

const user2 = new User("Taro", "Suzuki");
console.log(user2.getFullName()); //=> Taro Suzuki

thisについても先に紹介したオブジェクトのメソッドと同様に扱われます。
user2.getFullName() と呼び出した際のthisはuser2のインスタンスになるので、コンストラクタでuser2の firstName、lastName に格納していた名前がgetFullNameのthisで取得できます。

 

コラム:「呼び出されたときの状態」が大切です

ところで、JavaScriptは関数を代入できるので以下のような操作が可能です。

const user3 = {
  firstName: "Jiro",
  lastName: "Tanaka",
};

user3.getName = user.getFullName; // userが持つgetFullNameをuser3に代入
console.log(user3.getName()); //=> jiro Tanaka

getFullNameは先にuserオブジェクトのメソッドとして作成しましたが、その関数をuser3のgetNameとして代入することができ(1)、user3.getName() と呼び出せます(2)。関数の実体はgetFullNameと同じなので全く同じ動作をしますが、このときのthisは「呼び出されたとき」の所有者オブジェクトを指すので、userではなく呼び出し元オブジェクトであるuser3です。

このように書くことはできますが、特別な意図なしに濫用しない方が良いでしょう。例えばuser.getFullNameが書かれたコード周辺だけ読んだとき、user3.getNameとして代入して利用される想定をするのは困難です。そのため不用意にuserの実装を修正してuser3.getNameの呼び出し時にエラーになるコードを書いてしまいがちです。実装を再利用したいだけなら、関数やクラスを新たに作ると、影響範囲がわかりやすいコードになるでしょう。

また、他人の書いたコードを読む際に「thisが予測されるものと違う可能性」を疑うことは必要です。thisがあると考えることが増えるため、自分で使用するときも意識しましょう。

 

ポイント4:コンストラクタ関数のthis(重要度★)

最近はあまり利用されなくなりましたが、少し古いコードに触れる際は知っておくといいトピックです。JavaScriptにクラスが導入される前は、以下のような要領で”クラスのようなもの”を作成しました。

// MyClassクラスのコンストラクタ
function MyClass() {
  this.name = "これはMyClassです"; //---(1)
}

// testメソッドの定義
MyClass.prototype.test = function () {
  console.log(`test: ${this.name}`);
};

const instance = new MyClass(); //---(2)
instance.test(); //=> test: これはMyClassです

(2)のところで 「new MyClass()」とnewを付けて呼び出し、 インスタンス化しています。MyClass関数におけるthisは「newの結果返される新しいインスタンス」を指し、instanceと(1)のMyClass関数における thisが一致する仕様になります。

今はトランスパイルされた結果のコード等で見ることがあるかもしれません。ES6が利用できる環境ではこの記法を使わず、クラスで書くと良いでしょう。

 

ポイント5:アロー関数のthis(重要度★★★)

ここまで「『呼び出されたときの状態』によって、指し示すオブジェクトが変化する」というthisの基本的な動作を紹介しました。ただ、thisの指し先を変化させたくないときは困ります。そんなときの解決法としてアロー関数を紹介します。

アロー関数というと、関数を記述するfunctionの代替のようなイメージを持たれている方もいるかもしれませんが、このふたつはthisの選ばれ方が異なります。

以下の例とともに確認してみましょう。

{
  const user = {
    firstName: "Taro",
    lastName: "Suzuki",

    setBodyAlert: function () {
      // アロー関数がない時代はこう書いていました
      const that = this;
      document.body.addEventListener("click", function () { //---(1)
        console.log(this); // 残念ながらこのthisはuserインスタンスではありません
        alert(that.firstName);
      });

      // アロー関数のおかげで、setBodyAlertのthisを利用できます
      document.body.addEventListener("click", () => { //---(2)
        alert(this.firstName);
      });
    },
  };

  user.setBodyAlert();
}

(1)のコードはイベントリスナをfunctionで書いています。function内のthisはuserインスタンスではないので、thatのような別の変数が必要になりました。このような上位スコープのthisを入れた変数宣言が頻発すると読みにくいですね。

この課題の解決で最もよく用いられるのがアロー関数( () => )です。アロー関数は 「”関数を定義したスコープのthis”でthisが設定されます(しかも変更できません)。」

(2)のコードは(1)と同じ動作ですが、アロー関数を使ってスッキリと書けています。有用性が伝わったでしょうか。しかもthisの変更可能性がないので、関数定義のコードを読めばthisを特定することができます。扱いやすいですね。

 

ポイント6:call/apply/bindでの明示的なthis(重要度★★)

こちらはFunctionで利用できる関数を用いてthisを設定する方法です。

// callで強制的にthisを変えることができます
console.log(user.getFullName.call(user3)); //=> jiro Tanaka

user.getFullNameの実行時のthisはcallの第一引数であるuser3となり、呼び出しのthisを設定できます。call/apply/bind の違いは、以下の表を参考にしてください。

callやapplyは以下に示すシーンでよく使われましたが、現在ではより良い記法が登場して書く機会はだいぶ減っています。

 

今回は初心者がつまずきやすいthisについて解説しました。JavaScriptにおいて、thisが呼び出されたときの状態によって指し示すオブジェクトが変化することがわかると、今後他人の書いたコードを読むときはもちろん、自分でも自信を持って書けるようになります。
次回はクロージャについて学びます。直観的に理解ができても使いこなすのは難しいと言われていますが、具体的なコードを見ながら丁寧に学習していきましょう。次回もお楽しみに。

記事内に使用されたソースコードは以下からも確認いただけます。
https://github.com/CircleAround/engineer_style_js