Javascript アロー関数について調べてみた件

Javascript アロー関数について調べてみた件

ES6より登場したアロー関数。
最初は短くかけてすごく便利だと思って使っていましたが、使っていくうちに間違いに気付きました。
関数リテラルのシンタックスシュガー的な物をアロー関数だと思って使っていたからです。特にthisの使い方でハマることが多かったのでおさらいしたいと思います。
アロー関数を正しく使えるよう検証をしていきます。

アロー関数の書き方

アロー関数は以下のように書きます。

(引数) => {処理内容}

アロー関数を使ってみよう


次に簡単な計算処理を行い画面に表示させてみたいと思います。

<!-- 省略 -->
<div class="p-arrowfunc">
  <div class="p-arrowfunc__view js-view">
  </div>
</div>
<!-- 省略 -->
const view = document.getElementsByClassName('js-view')[0];

const DisplayView = (a, b) => {
  const text = a + ' ' + b;
  return view.textContent = text;
}

DisplayView('Hello', 'World!');

Hello Worldが出力されたかと思います。
書き方が短くなった以外は関数リテラルとそう変わりはありません。

アロー関数の特徴

  • thisをバインドできる(レキシカルスコープ)
  • 名前付き関数を作れない
  • インスタンスが作れない

この中で特にthisのバインドに関しては特に注意が必要かと思います。

アロー関数のthisの挙動を探ってみよう

先ほどのサンプルを書き換えthisの挙動をみてみます。
jsは挙動を確かめるため先に通常の関数で試します。
押されたボタンのテキストを表示させるだけの関数です。

<!-- 省略 -->
<div class="p-arrowfunc">
  <div class="p-arrowfunc__view js-view">
  </div>
</div>
<button class="js-button">Button1</button>
<button class="js-button">Button2</button>
<!-- 省略 -->
const view = document.getElementsByClassName('js-view')[0];
const btns = document.getElementsByClassName('js-button');

const DisplayView = (a) => {
  const text = a;
  return view.textContent = text;
}

Array.from(btns).forEach(btn => {
  btn.addEventListener('click', function () {
    DisplayView(this.textContent);
  });
});

意図した通りに動いているかと思います。
次にアロー関数に書き直してみます。

const view = document.getElementsByClassName('js-view')[0];
const btns = document.getElementsByClassName('js-button');

const DisplayView = (a) => {
  const text = a;
  return view.textContent = text;
}

Array.from(btns).forEach(btn => {
  btn.addEventListener('click', () => {
    DisplayView(this.textContent);
  });
});

エラーになるかと思います。
エラー内容を確認してみると’Cannot read property ‘textContent’ of undefined’と出ています。thisがundefinedになっているようです。
ここが通常の関数と比べthisの扱いが難しいと感じるところです。
実際にどうすればいいかというと今回の場合は直接HTMLcollectionの配列を渡してあげればいいので以下のように書き直します。

const view = document.getElementsByClassName('js-view')[0];
const btns = document.getElementsByClassName('js-button');

const DisplayView = (a) => {
  const text = a;
  return view.textContent = text;
}

Array.from(btns).forEach(btn => {
  btn.addEventListener('click', () => {
    DisplayView(btn.textContent);
  });
});

これで通常の関数と同じ挙動になるかと思います。
このままでは特にアロー関数のメリットがないように感じます。
それではアロー関数が便利なところとはどんなところなのでしょうか。
結論からいうとやはりthisがバインドできるところだと思います。
以下のテストケースをご覧ください。

<!-- 省略 -->
<div class="p-arrowfunc">
  <div class="p-arrowfunc__view js-view">
  </div>
</div>
<button class="js-button">Button</button>
<!-- 省略 -->
class Counter {
  constructor() {
    this.count = 0;
    const btn = document.getElementsByClassName('js-button')[0];
    const txt = document.getElementsByClassName('js-view')[0]
    btn.addEventListener('click', function() {
      this.count += 1;
      txt.textContent = this.count;
    });
  }
}

new Counter;

btnのクリックイベントで通常の関数を使いました。
このケースではエラーになります。btn内で定義しているthisがbtnオブジェクトを指しているからです。

thisは定義する場所によってその値が変わります。ですので定義する場所によってthisがなんなのかを調べないと思わぬバグを作ってしまうことになります。
アロー関数は最初に定義された場所からthisをbindしてくれます。ですのでスコープ内でthisの値が固定されバグの温床を減らすことにつながります。
以下アロー関数を使い修正したコードになります。

class Counter {
  constructor() {
    this.count = 0;
    const btn = document.getElementsByClassName('js-button')[0];
    const txt = document.getElementsByClassName('js-view')[0]
    btn.addEventListener('click', () => {
      this.count += 1;
      txt.textContent = this.count;
    });
  }
}

いかがでしたでしょうか。
これだけだとアロー関数の仕様とどういうケースがベストなのかまだまだ不明だとは思いますが。
関数リテラルを短い形でかける、さらにthisのバインドという強烈な機能がアロー関数の特徴かと思います。
よりよい使い方があれば探して載せていこうと思っていますが今回はここまでになります。