blog @arfyasu

プログラミングとか趣味のこととか

JavaScript の this って分かりにくいけど ES2015 で何か変わったの?

はじめに

JavaScript の勉強のために、開眼!JavaScript を読んだ。

開眼!  JavaScript ―言語仕様から学ぶJavaScriptの本質

開眼! JavaScript ―言語仕様から学ぶJavaScriptの本質

JavaScriptの理解があやふやだった自分にとって、知らないことがたくさんあり勉強になることが多かった。
その中でも、this についての理解があやふやだったので、復習もかねて内容をまとめておこうと思う。

一般的な使い方

var User = function(name) {
    this.name = name;
}
var bob = new User('bob');
console.log(bob.name);    // 出力: bob

この this は、生成されたインスタンス自身を指します。
このの使い方が、最も一般的でしっくりくる使い方だと思う。

入れ子関数内で使われる場合

var myNumber = {
  elements: [1, 2, 3],
  multiple: 3,
  multiply: function() {
    console.log(this.elements);    //  出力: [1, 2, 3]
    var multipleElements = this.elements.map(function(element) {
      return element * this.multiple;
    });
    console.log(multipleElements);  //  出力: [NaN, NaN, NaN]
  }
};

myNumber.multiply();

multiply は、elements の各要素に multiple の値を乗算した結果を出力するメソッドです。

実行すると、[3, 6, 9] が出力されることを期待しますが残念。
[NaN, NaN, NaN]と出力されます。

これ、私が最もよくやってしまう間違いでした。
最初の頃は、ほんと何が間違っているのかさっぱり分かりませんでした。

入れ子関数内の this はグローバルオブジェクトを参照

先ほどのコードの乗算処理中に this を出力してみます。

    var multipleElements = this.elements.map(function(element) {
      console.log(this);  // 出力: Window /_display/
      return element * this.multiple;
    });

っと、このようにグローバルオブジェクトである window を参照してしまいます。
なんとも不思議です。

グローバルオブジェクトに multiple は未定義なので、NaN が返ってきていたということですね、はい。

解決方法

これもお決まりで、スコープチェーンを使って解決します。
親関数内に自身を参照できる変数を用意し、入れ子関数からはその変数を経由して参照します。

以下が修正版です。

var myNumber = {
  elements: [1, 2, 3],
  multiple: 3,
  multiply: function() {
    console.log(this.elements);  //  出力: [1, 2, 3]
    var self = this;  // myNumber への参照をセット
    var multipleElements = this.elements.map(function(element) {
      return element * self.multiple;
    });
    console.log(multipleElements);  //  出力: [3, 6, 9]
  }
};

myNumber.multiply();

ローカル変数 self を用意し、そこに myNumber への参照をセットします。
入れ子関数から multiple の値を取得する際、この self を参照して取得します。

ちなみにこの仕様は、ECMAScript 5 で解決するということが記載されていました。

ES2015(ES6) の Arrow functions

最近、React の勉強がてら ES2015 でコードを意識して書いています。
その中で、上記の this が解決されているのか確認してみました。

結論を先に書くと、Arrow function 記法で書けば解決です!

Arrow functions 記法は、

list.map(function(i) { return i * i; });

を、以下のように書くことができます。

list.map((i) => i * i);

この Arrow functions 書き方も簡略化されていて見やすいですね。
その上、this の参照先がグローバルオブジェクトにならないので便利です。

ということで、最初のコードはスコープチェーンを使わなくても良くなります。

var myNumber = {
  elements: [1, 2, 3],
  multiple: 3,
  multiply: function() {
    console.log(this.elements);  //  出力: [1, 2, 3]
    var multipleElements = this.elements.map((element) => element * this.multiple);
    console.log(multipleElements);  //  出力: [3, 6, 9]
  }
};

myNumber.multiply();

Arrow functions については、以下のページに色々説明があります。
英語が分からなくても、コードが読めれば大丈夫w

developer.mozilla.org

まとめ

ということで、this の内容のまとめでした。

Arrow functions で解決されたのが分かった時には「おぉ」ってなりましたw
今後は、こっちを使っていきます!

ES3 とか使わないといけないプロジェクトにアサインされた場合は、気をつけよう、うん。

開眼!  JavaScript ―言語仕様から学ぶJavaScriptの本質

開眼! JavaScript ―言語仕様から学ぶJavaScriptの本質