#概要
Javascriptを学んでいると度々現れる『this』という概念。
複雑でよくわからない...という初学者の方、多いのではないでしょうか?
私はこれを理解するのにかなり時間がかかりました...
色々な記事を漁った結果、基本概念や関連用語について詳しく説明されているとすんなり理解出来るなぁと思いましたので、備忘録 兼 同じ学習フェーズの方の一助になればと考えまとめてみました。
なお、今回の記事では、関数式と関数宣言におけるthisを理解するという部分に焦点を充てているため、classオブジェクトのコンストラクタ関数については記載を省いております。ご了承ください。
###対象読者
・Javascriptを学び始めた方
・thisについて何度調べてもなかなか理解出来ない、という方
####今回記述すること
・thisの基本概念
・関数やメソッドといった関連用語の説明
・混乱しがちなケース
・thisの束縛方法
####今回記述しないこと
・コンストラクタ関数内でのthis
・アロー関数でのthis
それでは始めていきたいと思います。
##そもそもthisって何?
一言でいうと、thisは__オブジェクトを参照するためのキーワード__です。
もう少し具体的にいえば、Javascriptに最初から用意されていて、プログラム内のどこでもいつでも単体で利用することが出来る特別な変数のことを指します。
他の言語で扱ったことのある方なら、thisがどんなものかイメージ出来るかもしれませんが、初めて触れる方はそもそもthisって何に使うの?って思いますよね。
まずは以下のコードを見てください。
const user = {
firstName: "太郎",
lastName: "田中",
getFullName: function () {
console.log(this) //実行結果: {firstName: "太郎", lastName: "田中", getFullName: ƒ}
return `${this.lastName} ${this.firstName} `;
}
}
const userName = user.getFullName();
console.log(userName); //実行結果: "田中 太郎"
上記のコードでは、userというオブジェクトのなかにgetFullNameという関数(メソッド)を定義し、その中で苗字と名前を繋げてフルネームとして返却する処理を書いています。
console.log(this)
return `${this.lastName} ${this.firstName} `; //thisでuserオブジェクトの情報を参照している
このように、内部で行う処理に必要な情報をオブジェクトから持ってくるためにthisを使用することがあります。
thisを使うことで、再定義しなくともオブジェクトの値を参照して処理を行うことが出来ます。
これにより、値の変更があったときに関数内のコードを書き直す必要がなく、コードのメンテナンスがしやすくなります。
試しにオブジェクトの中身を書き換えた結果がこちらです。
const user = {
firstName: "花子",
lastName: "鈴木",
getFullName: function () {
return `${this.lastName} ${this.firstName} `;
}
}
const userName = user.getFullName();
console.log(userName); // => 実行結果が "鈴木 花子"に変わります
ただ、thisは呼び出されたタイミングや状況によって参照先がコロコロと変わるので慣れていないとこんがらがってしまいがちで、理解しづらいところです。
それでは掘り下げて記述していきます。
以下のコードを見てください。
const user = {
firstName: "太郎",
lastName: "田中"
}
function getFullName() {
console.log(this) //実行結果: Window {window: Window, self: Window, document: document, name: "", location: Location, …}
return `${this.lastName}${this.firstName}`;
}
const userName = getFullName();
console.log(userName); //実行結果: undefined
先ほど記述したものと似ていますが、こちらのコードだと実行結果はundefinedになります。
function getFullName(firstName, lastName) {
console.log(this) //実行結果: Window {window: Window, self: Window, document: document, name: "", location: Location, …}
return `${lastName}${firstName}`;
}
着目して頂きたいのはここです。最初に挙げたコードと同じようにconsole.logでthisの中身をみてみると、今回はuserオブジェクトではなく Windowオブジェクト(詳しくは後述します)
というものが返ってきています。これはどういうことでしょうか?
この挙動の違いを理解することが、正しくコードを書く重要なポイントとなります。
そしてthisについて混乱しないために大切なもうひとつのポイントがあります。
それは、 関連用語をしっかりと区別して認識しておくこと
です。
Javascriptでは関数とメソッドという類似の用語があり、この2つを混同してしまうことが理解し難い要素だと思います。
##関連用語について
###関数とは?
参考: 関数 - JavaScript | MDN - Mozilla
関数とは外部 (再帰の場合は内部) から 呼び出す ことのできる「サブプログラム」です。プログラムそのもののように、関数は関数本体 (function body) と呼ばれる連続した文で構成されます。値を関数に 渡す ことができ、関数は値を返すことができます。
上記の内容を噛み砕いて書くと、同じ処理をひとつの関数という「サブプログラム」として一箇所で定義したもの
です。そして、1回1回同じ処理を書く代わりに、その関数を呼び出すという形で置き換えることで、コードを見やすく書きやすい状態にします。
さらに、その処理の部分に共通の変更点が生じた際にも、変更が一箇所で済むというメリットもあります。
###メソッドとは?
参考: [オブジェクトでの作業 - JavaScript | MDN]
(https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Working_with_Objects)
メソッドはオブジェクトに関連付けられた関数です。簡単に言えば、オブジェクトのプロパティである関数です。メソッドは通常の関数と同じ方法で定義されますが、オブジェクトのプロパティに代入される点が異なります。
要するに、オブジェクトのプロパティに代入された関数のこと
を言います。
また、プロパティはオブジェクト自身が持っている特性を示す情報のことです。
あるオブジェクトのプロパティに関数を定義した場合、関数と呼ばずにそのオブジェクトのメソッドと呼びます。
いわば、オブジェクトさんという人が持っている特技のようなものです。
メソッドも関数の1つなのですが、今回のthisのような複雑な概念を説明しやすくするために、便宜上呼び方を切り分けているのです。
ちなみに、先ほど出てきたWindowオブジェクトについても簡単に記述します。
Windowオブジェクトはグローバルオブジェクトとも呼ばれ、Javascriptにもともと備わっているオブジェクトです。Javascriptのコードは様々なオブジェクトが組み合わさって出来ているのですが、グローバルオブジェクトは、その全てを格納している一番最上位のオブジェクトなのです。グローバル関数や定数をいつでも使えるように準備しており、let/constなどを使ってユーザーが定義した変数や、関数などを保管する役割も持っています。
グローバルオブジェクトには
・関数: map, forEach, setTimeout etc...
・定数: undefined, NaN等
といったよく見るものが沢山保管されているのです。
実際に見ると理解しやすいかなと思いますので、関数などに囲まれていないところでconsole.log(this)と書き、windowオブジェクトの中身を確認してみるといいと思います。
それでは、これらを把握した上でもう一度最初の例を見てみましょう。
const user = {
firstName: "太郎",
lastName: "田中",
getFullName: function () {
console.log(this) //実行結果: {firstName: "田中", lastName: 太郎", getFullName: ƒ}
return `${this.lastName} ${this.firstName} `;
}
}
const userName = user.getFullName();
console.log(userName); //実行結果: "田中 太郎"
こちらがuserオブジェクトのメソッドで
const user = {
firstName: "太郎",
lastName: "田中"
}
function getFullName() {
console.log(this) //実行結果: Window {window: Window, self: Window, document: document, name: "", location: Location, …}
return `${this.lastName}${this.firstName}`;
}
const userName = getFullName();
console.log(userName); //実行結果: undefined
こちらは関数、ということです。getFullName関数のなかで、「え、オブジェクトどこよ?」となり、グローバルオブジェクトまで探しに出てwindowオブジェクトを返している、という感じです。
これで少しイメージしやすくなったのではないでしょうか?
##thisの参照先ってどう見分けるの?
結論からいうと
###・呼ばれたのがメソッドの場合、その呼び出し元のオブジェクト
(〇〇.□□と呼び出している〇〇の部分)
###・呼ばれたのが関数の場合、Windowオブジェクト
(そもそも関数を呼ぶ際は『.』がありません)
がそれぞれthisの参照先となります。
私はこの覚え方が一番イメージしやすいのではないかと思います。
一部混乱しやすい場面があるため、それも後述してきます。
#混乱しやすいケース
####メソッドのなかの関数
以下のコードを見てみましょう。
var user = {
name: "田中太郎",
getName: function() {
function addGreet() {
console.log(this) //実行結果: Window {window: Window, self: Window, document: document, name: "", location: Location, …}
return `hello! ${this.name}さん`
}
const greet = addGreet();
console.log(greet); //実行結果: hello! undefinedさん
}
}
user.getName();
呼ばれたのがメソッドの場合その呼び出し元のオブジェクト、と言いましたが、メソッドのなかでさらに関数を定義した場合、呼び出し元は関数のためthisはwindowオブジェクトを参照するようになっています。結果、返却された値の部分がundifinedとなっています。
####メソッドチェーン
var user = {
firstName: "太郎",
lastName: "田中",
getFullName: function () {
return this.lastName + this.firstName;
},
greet: function (msg) {
console.log(`${ msg } 、 ${this.lastName} ${this.firstName}さん`)
}
}
const fullName = user.getFullName();
console.log(fullName); //実行結果: "田中 太郎"
user.getFullName().greet("こんにちわ"); //実行結果: Uncaught TypeError: user.getFullName is not a function
この例ではgetFullNameメソッドで返ってきたfullNameに挨拶を付け足して表示するメソッドを実装しようとしていますがエラーが発生しています。
複数のメソッドを数珠つなぎで呼び出すメソッドチェーンでは、1つ目のメソッドで返ってきた値のなかのオブジェクトを参照しようとします。
上記の例の場合、メソッドで返ってくる値は"田中太郎"という文字列のため、undefindとなりエラーが発生しています。
○○.□□と呼び出し元があっても、その中身にオブジェクトがなければ正しく処理されないため、ここには注意が必要です。
##thisの参照先を指定する方法
いくつか事例を挙げましたが、thisを正しく参照出来ない場合にも、明示的にthisの参照先を指定してあげることで正しく動かすことが出来ます。その方法は、以下の3通りあります。
####bindメソッドを使う
var user = {
name: "田中太郎",
getName: function() {
console.log(this); //実行結果: {name:"田中太郎", getName: ƒ...}
function addGreet() {
return `hello! ${this.name}さん`
}
const Greet = addGreet.bind(this); //addGreet関数のthisで参照する値を、今いるgetNameメソッドの参照先であるuserオブジェクトにするように設定
console.log(Greet());
}
}
user.getName();
bindとは、関数に対してthisや引数指定することができるメソッドです。
関数に対して『今の場所で参照しているthisを同じように使ってね』と設定した新しいメソッドを作って実行しているという感じになります。
これにより、同じプログラムでも振る舞いを変えることができます。
####callメソッド、applyメソッドを使う
var user = {
name: "田中太郎",
getName: function() {
function addGreet() {
console.log(this)
return `hello! ${this.name}さん`
}
const greet = addGreet.call(this); // or const greet = addGreet.apply(this);
console.log(greet); //実行結果: hello! 田中太郎さん
}
}
user.getName();
callメソッド/applyメソッドもbindメソッドと同じような書き方なのですが、call/applyの場合は新しい関数を作成するのではなくそのまま関数を実行することができます。
applyメソッドは、( )の中で引数をとるときに配列で書く、という違いがあるのですが、今回のような場合はどちらも同じ書き方で問題なく動きます。
また、bindもこの2つのメソッドも、指定するものはthisでなくても大丈夫です。
addGreet関数のなかではthis.nameを参照しようとしているため、オブジェクトであれば別の値を与えてあげても動きます。
試しに書き換えてみます。
var user = {
name: "田中太郎",
getName: function() {
function addGreet() {
return `hello! ${this.name}さん`
}
const Greet = addGreet.bind({name: "鈴木花子" });
console.log(Greet()); // 実行結果: hello! 鈴木花子さん
}
}
user.getName();
####thisを他の変数に代入して使う
var user = {
name: "田中太郎",
getName: function() {
console.log(this) //実行結果: {name:"田中太郎", getName: ƒ...}
const _that = this; //① 変数 _thatとしてthis(userオブジェクト)を代入し
function addGreet() {
console.log(_that) //実行結果: {name:"田中太郎", getName: ƒ...}
return `hello! ${_that.name}さん` //② 持ってきたuserオブジェクトを参照
}
const result = addGreet();
console.log(result); // 実行結果: hello! 田中太郎さん
}
}
user.getName();
上記のように、あらかじめ別の変数にthisの中身を保持しておきそれを参照するという方法です。
ちなみに、いままで書いてきたコードをアロー関数に変更すると挙動が変わってきます。
アロー関数が生まれた背景には
①記法を簡略化し書きやすくすること
②thisのしがらみを解消すること
という2つの目的があり、仕様が関数宣言や関数式と異なるためです。
今回の記事では、関数宣言と関数式にまつわるthisについてまとめたかったため、アロー関数でのthisについては触れておりません。申し訳ございません。
今後、学習を進めながらアロー関数についても別記事にまとめたいと思います。
また、どの書き方がベストかという点についても、私自身の経験や見解が足りないため、今後しっかり学び実務での経験を踏まえて追記出来ればと考えています。
##最後に
以上、thisの基本概念についてまとめてみました。
自分なりにまとめてみたのですが、認識が間違っている内容があるかもしれません。その場合は、大変恐縮ですがご指摘頂けると嬉しいです。