##始めに
今年こそはちゃんとES6の書き方を身につけたい!何となくで書くのを卒業したい!(Reactも実務で使いこなせるようになりたい!)という事で、手始めにJSの挙動で、理解がイマイチなポイントを言語化して理解していこうと思います。今回はアロー関数
とthis
について、MDNを読みながら、まとめました。
##対象読者
・JavaScript初心者
・これからES6の記法を勉強したいと思っている方
##アロー関数とは
アロー関数はES6から使えるようになったJSの構文の一つで、無名関数の省略記法です。
以下の理由から導入されました。
2 つの理由から、アロー関数が導入されました。
1 つ目の理由は関数を短く書きたいということで、
2 つ目の理由は this を束縛したくない、ということです。
MDN アロー関数
まずは、無名関数を短く書くという側面から。
ES5
までの関数の書き方と比較すると以下のようになります。
##ES5での関数の書き方
const normalAddFunc = function(a, b) {
return a + b;
}
##アロー関数で書く理由① 関数を短く書きたい
//アロー関数
const arrowAddFunc = (a, b) => {
return a + b;
}
//1個しか評価項目が無い場合は以下のようにも書ける
//中括弧とreturnの省略が出来る
const arrowAddFunc = (a, b) => a + b;
//評価項目が1個だけ、かつ引数が1個しか無い場合はこのようにも書ける
//括弧も省略できる
const arrowDoubleFunc = a => 2 * a;
省略して、1行で書ける事も出来ますが、アロー関数を書き慣れていない人がパッと見た時に、やや分かりづらいかもしれません。必ずしも1行で書かなければいけない理由はありません。いずれにしろ、 function
をいちいち書く必要がなく、短く書く事が出来ます。しかしアロー関数の真価は thisを束縛しない
という点にあるでしょう。
##アロー関数で書く理由② thisを束縛しない
アロー関数で短く書けます!と説明されても、へーそうなんだ。。。で終わると思いますが、アロー関数を使う上でちゃんと理解しておきたいのは、こちらの方かと思います。
それは、thisの値は関数定義時に決まる(=thisを束縛しない)
というルールです。
thisを束縛しない、という説明はやや分かりづらいので、ここでは、アロー関数を使えば、thisの値は関数定義時に決める事ができる、という理解で大丈夫でしょう。
JavaScriptでthis
を扱う時、最初は分かりづらい...直感的でない...という事は、JavaScriptを書いてる方なら、分かって頂けるかなと思います。this
の呼び出しパターンにはいくつかあるのですが、ここでは、アロー関数を使うメリットを説明する上で、メソッド呼び出しパターン
と関数呼び出しパターン
を事前に例として挙げておきます。this
を理解する上で大切なのは、呼び出し元が何であるか
という事です。まずはメソッド呼び出しパターンから説明します。
###メソッド呼び出しパターン
以下のコードを実際に打ち込んで、console.log
で確認してみましょう。
const object = {
value: 'test',
method1: function () {
return this;//①何が返されるでしょう?
},
method2: function () {
return this.value;//②何が返されるでしょう?
}
};
//① thisはobject自身を参照している
console.log(object.method1());//{ value: 'test', method: [Function: method] }
//② thisはobject自身を参照しているので、valueプロパティにもアクセスできる
console.log(object.method2());// test
メソッドの中から、同じオブジェクトに属している別のプロパティにthisを使ってアクセス出来ていますね。
以下の場合は何が出力されるでしょう?
const guitarObject = {
maker: "Fender",
model: "Telecaster",
year: 1964,
color: 'Black',
showMaker: function() {
console.log(this.maker);
}
};
guitarObject.showMaker();//何が出力されるでしょうか?
メソッドは何かしらのオブジェクトに属しています。(JavaScriptでは、オブジェクトのプロパティとして定義される関数の事をメソッドと呼ぶ)
guitarObject
のshowMakerメソッド
を呼び出してるので、答えはFender
ですよね。これは理解しやすいかなと思います。メソッドが属しているオブジェクトのプロパティをオブジェクト名.プロパティ名(例:guitarObject.maker)
の代わりにthis.プロパティ名(例:this.maker)
で参照できます。
(プロパティにアクセスする方法として、ドット記法の他にブラケット記法があります。興味がある人は調べてみて下さい)
この事から、メソッド呼び出しパターン
では、this
は関数を呼び出すオブジェクトを参照する、という事が分かりますね。
次に関数呼び出しパターン
を見てみます。
以下のコードを見ると、メソッド呼び出しパターンと違うのは、.[ドット]
で呼ばれてるかどうか、のみです。さて、この場合consoleに出力される値は何でしょうか。
###関数呼び出しパターン
function showMaker() {
this.maker = 'Gibson';
console.log(this);
}
showMaker();
答えは...
ブラウザならwindowオブジェクト
Node.jsならglobalオブジェクト
となります。
Object [global] {
global: [Circular],
clearInterval: [Function: clearInterval],
clearTimeout: [Function: clearTimeout],
setInterval: [Function: setInterval],
setTimeout: [Function: setTimeout] { [Symbol(util.promisify.custom)]: [Function] },
queueMicrotask: [Function: queueMicrotask],
clearImmediate: [Function: clearImmediate],
setImmediate: [Function: setImmediate] {
[Symbol(util.promisify.custom)]: [Function]
},
maker: 'Gibson'//追加されてるのが確認できる
}
この場合のthis
はグローバルオブジェクトを参照します。グローバルオブジェクトは、実行環境において、異なるものが定義されます(windowオブジェクト/globalオブジェクト)。
この事からも分かるように、普通の関数(アロー関数でない関数)の場合、this
の定義は定義した時点でなく、実行時に決まります。(←ここ重要)
その為、この挙動に慣れないで、thisを含む関数を書いた場合に、意図した呼ばれ方をされない、間違った結果が発生するという事が起こり得ます。
上記の事を踏まえて、次のコードを見てみましょう。
const guitarObject = {
maker: "Fender",
country: 'America',
model: "Telecaster",
year: 1964,
color: 'Black',
showMaker: function() {
console.log(this.maker); // ① 何が出力される?
const showCountry = function() {
console.log(this.country);// ② 何が出力される?
}
showCountry();
}
};
guitarObject.showMaker();
答えは...
①はFender
②はundefined
となります。
①は最初に見たメソッド呼びパターンです。
②はメソッド内で関数呼び出しをしているので、グローバルオブジェクトを参照してしまいます。当然、グローバルオブジェクトには、countryというプロパティは定義されていないので、undefinedとなります。
上記のような問題がある為、ES5まではこのような記述で、解決してきました。
const guitarObject = {
maker: "Fender",
country: 'America',
model: "Telecaster",
year: 1964,
color: 'Black',
showMaker: function() {
const self = this;// thisをスコープ変数に持たせる。selfにはguitarObjectが入る。
console.log(self.maker); // ① 何が出力される?
const showCountry = function() {
console.log(self.country);// ② 何が出力される?
};
showCountry();
}
};
guitarObject.showMaker();
//二つの値が出力される
Fender
America
もしくは...
const guitarObject = {
maker: "Fender",
country: 'America',
model: "Telecaster",
year: 1964,
color: 'Black',
showMaker: function() {
console.log(this.maker); // ① 何が出力される?
const showCountry = function() {
console.log(this.country);// ② 何が出力される?
}.bind(this);//bindメソッドを使用
showCountry();
}
};
guitarObject.showMaker();
//二つの値が出力される
Fender
America
こちらは、bindメソッド
を使って、thisを束縛しています。
この方法でも期待した値が出力されます。
このようにアロー関数が登場するまでは、関数ごとに、自身のthisの値を定義していました。
しかし、上記の方法を使って、thisの挙動をコントロールするのは面倒ですし、素直にコードを読んだ時にあまり直感的でないです。
そこでアロー関数が利用されます。
##アロー関数でのthisの挙動
const guitarObject = {
maker: "Fender",
country: 'America',
model: "Telecaster",
year: 1964,
color: 'Black',
showMaker: function() {
// thisはguitarObjectを参照する
console.log(this.maker);
// ※重要:アロー関数により、囲まれているコンテキスト(=ここでは、showMakerメソッド)のthis値が設定される
const showCountry = () => {
console.log(this.country);
};
showCountry();
}
};
guitarObject.showMaker();
アロー関数を使うと囲まれている実行コンテクストのthisの値を参照することができます。これはアロー関数を書く場所によってthisが確定される事と同意です。
(上のコードの場合は、showMakerメソッド
に囲まれてるので、アロー関数内のthis
もguitarObject
を参照する事ができます)
これは通常の変数を検索する際のルールに従っており、例えば、スコープ内にthisの値が無い場合は、その一つ外側のスコープでthisの値を探します。これはアロー関数自身がthisを持っているのでなく、レキシカルスコープ(静的なスコープ)
のthisの値を使っている事を意味します。
呼び出される文脈によってthisの値が変化し、コードを読んだ時に直感的には分かりにくかった問題点が、アロー関数を書く事によって、解決されている事が分かるかと思います。
宣言した時点で、どのオブジェクトを参照するか確定させたいケースには、アロー関数を使った方が、分かりやすいコードになるかと思います。
一方で、呼び出されたオブジェクトによってthisの参照先を変えたいケースもあるかと、思うので、その際はfunctionを使うという形で使い分けできると良いかなと思いました。
##最後に
アロー関数の便利な理由を理解するには、this
の理解を深める事が大事です。私自身も、まだ理解が曖昧なところがあるので、引き続き、実際にコードを書きながら理解を深めていきたいと思います。
間違ってる点等ありましたら、ご指摘いただけるとありがたいです。