akiyoko blog

akiyoko の IT技術系ブログです

JavaScript のクラスと this 問題

最近ちょこちょこと趣味的に JavaScript を書いているのですが、クラスの書き方をいろいろ調べていたら、いわゆる「this問題」に出くわしたので、これを機にまとめてみようと思います。

クラスの書き方

まずは、JavaScript のクラスの書き方について。


www.kuma-de.com

にいろいろな書き方がまとめられていたのですが、


自分なりに一番しっくりきているのは ↓ のような書き方です。

"use strict";

(function() {

  // クラス
  var Position = function(code) {
    // コンストラクタ
    this.code = code;

    // メソッド
    this.getCode = function() {
      return this.code;
    };
  };


  var position = new Position("A-bAa-Cc-dA-d");
  console.log("code=", position.getCode());

})();



厳密には JavaScript にはクラスというものは存在しないのですが、newキーワードで関数を呼び出すと関数内部がコンストラクタとして機能するので、それを利用して疑似的なクラスとして扱うことができるわけです。



まあ、ここまではよしとしましょう。




this 問題

実装を進めていると、いわゆる「this 問題」に出くわしました。

  // クラス
  var Position = function(code) {
    // コンストラクタ
    this.code = code;
    this.uppercases = [];
    this.lowercases = [];
    this.code.split("").forEach(function (d, i) {
      if (/[A-Z]/.test(d)) {
        this.uppercases.push(d);
      } else if (/[a-z]/.test(d)) {
        this.lowercases.push(d);
      }
    });

    // メソッド
    this.getCode = function() {
      return this.code;
    };
    this.getUppercases = function() {
      return this.uppercases;
    };
    this.getLowercases = function() {
      return this.lowercases;
    };

  };


  var position = new Position("a-bAa-Cc-dA-d");
  console.log("code=", position.getCode());
  console.log("uppercases=", position.getUppercases());
  console.log("lowercases=", position.getLowercases());


実行すると、

Uncaught TypeError: Cannot read property 'uppercases' of undefined

というエラーが出てしまいます。


forEach 文中の「this」がグローバルオブジェクトを参照していて、9行目の this.uppercases が undefined になってしまっているのがエラーの原因です。





詳しくは、
qiita.com
qiita.com

などですっきりとまとめられているのですが、要するに、this のスコープはその呼び出され方によって変わってくる というのが私の理解です。

無名関数中の this のスコープには注意が必要で、特に、jQuery の .on() などで無名関数をイベントとしてバインドする際に、this 問題に悩ませられるケースが多いと思います。



上記のケースでは、

  // クラス
  var Position = function(code) {
    // コンストラクタ
    var self = this;  // Added
    this.code = code;
    this.uppercases = [];
    this.lowercases = [];
    this.code.split("").forEach(function (d, i) {
      if (/[A-Z]/.test(d)) {
        self.uppercases.push(d);  // Modified
      } else if (/[a-z]/.test(d)) {
        self.lowercases.push(d);  // Modified
      }
    });


Python っぽく(実際には self は that でも何でもいいのですが)書くか、あるいは、

  // クラス
  var Position = function(code) {
    // コンストラクタ
    this.code = code;
    this.uppercases = [];
    this.lowercases = [];
    this.code.split("").forEach(function (d, i) {
      if (/[A-Z]/.test(d)) {
        this.uppercases.push(d);
      } else if (/[a-z]/.test(d)) {
        this.lowercases.push(d);
      }
    }.bind(this));  // Modified


と書けば解決するのですが、個人的には後者の方がしっくりきています。
あと、self に代入する方法は最近はあまり使われていないようです。


九章第五回 クロージャ — JavaScript初級者から中級者になろう — uhyohyo.net
に詳しく書かれています。


JavaScript の this は、慣れるまでちょっと厄介ですね。


(参考)

よくまとまっているので、オススメ。
bonsaiden.github.io


JavaScript逆引きレシピ jQuery対応

JavaScript逆引きレシピ jQuery対応