104
83

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

アロー関数が便利な理由と使いどころ

Last updated at Posted at 2020-01-11

##始めに
今年こそはちゃんとES6の書き方を身につけたい!何となくで書くのを卒業したい!(Reactも実務で使いこなせるようになりたい!)という事で、手始めにJSの挙動で、理解がイマイチなポイントを言語化して理解していこうと思います。今回はアロー関数thisについて、MDNを読みながら、まとめました。

##対象読者
・JavaScript初心者
・これからES6の記法を勉強したいと思っている方

##アロー関数とは

アロー関数はES6から使えるようになったJSの構文の一つで、無名関数の省略記法です。
以下の理由から導入されました。

2 つの理由から、アロー関数が導入されました。
1 つ目の理由は関数を短く書きたいということで、
2 つ目の理由は this を束縛したくない、ということです。
MDN アロー関数

まずは、無名関数を短く書くという側面から。
ES5までの関数の書き方と比較すると以下のようになります。

##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では、オブジェクトのプロパティとして定義される関数の事をメソッドと呼ぶ)

guitarObjectshowMakerメソッドを呼び出してるので、答えはFenderですよね。これは理解しやすいかなと思います。メソッドが属しているオブジェクトのプロパティをオブジェクト名.プロパティ名(例:guitarObject.maker)の代わりにthis.プロパティ名(例:this.maker)で参照できます。
(プロパティにアクセスする方法として、ドット記法の他にブラケット記法があります。興味がある人は調べてみて下さい)

この事から、メソッド呼び出しパターンでは、thisは関数を呼び出すオブジェクトを参照する、という事が分かりますね。

次に関数呼び出しパターンを見てみます。

以下のコードを見ると、メソッド呼び出しパターンと違うのは、.[ドット]で呼ばれてるかどうか、のみです。さて、この場合consoleに出力される値は何でしょうか。

###関数呼び出しパターン

関数呼び出しパターン①
function showMaker() {
    this.maker = 'Gibson';
    console.log(this);
}

showMaker();

答えは...
ブラウザならwindowオブジェクト
Node.jsならglobalオブジェクトとなります。

node.js_出力結果
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まではこのような記述で、解決してきました。

thisを別の変数に持たせて利用する

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

もしくは...

bindメソッドを使って
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メソッドに囲まれてるので、アロー関数内のthisguitarObjectを参照する事ができます)

これは通常の変数を検索する際のルールに従っており、例えば、スコープ内にthisの値が無い場合は、その一つ外側のスコープでthisの値を探します。これはアロー関数自身がthisを持っているのでなく、レキシカルスコープ(静的なスコープ)のthisの値を使っている事を意味します。

呼び出される文脈によってthisの値が変化し、コードを読んだ時に直感的には分かりにくかった問題点が、アロー関数を書く事によって、解決されている事が分かるかと思います。

宣言した時点で、どのオブジェクトを参照するか確定させたいケースには、アロー関数を使った方が、分かりやすいコードになるかと思います。

一方で、呼び出されたオブジェクトによってthisの参照先を変えたいケースもあるかと、思うので、その際はfunctionを使うという形で使い分けできると良いかなと思いました。

##最後に
アロー関数の便利な理由を理解するには、thisの理解を深める事が大事です。私自身も、まだ理解が曖昧なところがあるので、引き続き、実際にコードを書きながら理解を深めていきたいと思います。

間違ってる点等ありましたら、ご指摘いただけるとありがたいです。

##参照記事
MDN アロー関数
MDN this
MDN クロージャ

104
83
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
104
83

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?