GASでselfを使って関数の中からインスタンスを表すthisを使えるようにする方法


みなさん、こんにちは!
タカハシ(@ntakahashi0505)です。

「初心者でもわかるGoogle Apps Scriptのクラス」をテーマにシリーズ連載をお送りしてまいりました。

前回の記事はこちらです。

Google Apps Scriptでオブジェクトに直接追加しているプロパティだけループする方法
「初心者でもわかるGoogle Apps Scriptのクラス」をテーマにシリーズでお送りしています。今回は、GASでオブジェクトに直接追加しているプロパティのみをループする方法をお伝えします。

GASでオブジェクトに直接追加しているプロパティについてのみループをする方法をお伝えしました。

それで、そのプロパティについてのみループしてログ出力するという機能もよく使いそうなので、メソッドとしてクラスに追加しちゃおうと思っています。

ですが、ちょっと簡単にはいかないことがありまして…

今回はそのへんを進めていきますよ。

GASでselfを使ってスコープの中からインスタンスを表すthisを使えるようにする方法です。

ナンノコッチャですよね…?

でも、行ってみましょう!

前回のおさらい

まずは、これまでのおさらいからです・

以下のようなスプレッドシートがあります。

スプレッドシートのテーブル

この一行ずつのデータを表すクラスを作成しました。

こちらのクラスPersonです。

(function(global){

  var Person = function(record){
    var _id = record[0];
    this.name = record[1];
    this.gender = record[2];
    this.birthday = record[3];
    
    Object.defineProperties(this, {
      id: {
        get: function(){
          return _id;
        }
      }
    });
  };
  
  Person.prototype.greet = function(){
    Browser.msgBox(this.name + "です、こんにちは!");
  };
  
  Person.prototype.log = function(){
    Logger.log('%s|%s|%s|%s|', this.id, this.name, this.gender, this.birthday);
  };

  global.Person = Person;

})(this);

それで、各行から生成したインスタンスを集合で扱うためのクラスを作りました。

それがこちらのクラスPersonsです。

(function(global){

  var Persons = function() {

    var values = SpreadsheetApp.getActiveSheet().getDataRange().getValues();
    values.shift();
    
    for(var i = 0; i < values.length; i++){
      var p = new Person(values[i]);
      this[p.id] = p;
    }      
  };
  
  Persons.prototype.add = function(record){
    var p = new Person(record);
    this[p.id] = p;
    return this[p.id];
  };
  
  Persons.prototype.remove = function(id){
    delete this[id];
  };
  
  global.Persons = Persons;

})(this);

それで、前回。

このPersonsクラスのインスタンスから、それに含まれるPersonオブジェクトのデータをログ出力で確認できるように、Object.keysメソッドを使って以下のような処理を作成しました。

function myFunction() {  
  
  var persons = new Persons();

  Object.keys(persons).forEach(
    function(id) {
      persons[id].log();
    }
  );  
    
}

今回のお題:「var self = this」とは?

それで、このPersonsオブジェクトの内容をログ出力するメソッドなのですが、よく使いそうなので、Personsクラスに追加しちゃおう!

というのが今回やりたいことです。

ただし、ちょっと引っかかるポイントがあります。

何が引っかかるポイントなのか、またその解決方法が「var self = this」であり、それはなぜかということについてお伝えしていきます。

「undefinedのメソッドを呼び出せません」

では、ひとまずストレートにメソッドを追加してみましょう。

上記のmyFunctionの5~9行目を、コンストラクタPersonsのprotorypeプロパティにlogメソッドとして追加していきます。

コンストラクタPersonsでいうと、24行目あたりに以下のようなコードを挿入すれば良さそうに思えます。

  Persons.prototype.log = function() {
    Object.keys(this).forEach(
      function(id) {
        this[id].log();
      }
    );  
  }

では、myFunctionを以下のように変更して、実行してみましょう。

function myFunction() {  
  
  var persons = new Persons();
  persons.log();
    
}

おや…?

関数の中のthisでエラーになってしまう
ダメですね。

「undefinedのメソッド「log」を呼び出せません。」というエラーが出てしまいます。

thisは場所によって参照するものが異なる

ここでいう「this」はPersonsクラスのインスタンスを表していてほしくて、ブラケット(角括弧)の中にidを指定しているので、これでPresonオブジェクトを取り出しているはず…

そして、Personオブジェクトにはlogメソッドがあるので、それを呼び出せるはず…

と思っていたのですが、ちょっと勝手が違うようです。

このエラー、原因を知るのは難しいのですが、forEachメソッドの引数として指定されている関数が原因です。

その中に「this」があるのですが、実はこの関数内のthisはPersonsオブジェクトではなく、グローバルオブジェクトを表します。

thisの場所と参照先の対応

thisは場所によって参照するものが異なるのです。

表にすると以下になります。

thisの場所 thisが参照するもの
グローバル グローバルオブジェクト
コンストラクタ 生成したインスタンス
メソッド 呼び出し元のオブジェクト
関数 グローバルオブジェクト

つまり、コンストラクタ直下もしくはメソッド直下であれば、そのインスタンスを表すわけですが、その内部に含まれる関数の中に入ってしまうと、thisはグローバルオブジェクトになっちゃうというわけです。

なので、そもそもグローバルオブジェクトには「a01」とか「a02」といったプロパティが存在していないので、未定義つまりundefinedとなってしまうというわけです。

thisがグローバルオブジェクトを指していることを確認

では、forEachメソッドの関数内のthisがグローバルオブジェクトを指していることを確認してみましょう。

グローバル領域に以下のような、ステートメントを書いておきます。

this.a01 = 'hoge';
this.a02 = 'fuga';
this.a03 = 'foo';

これで、グローバルオブジェクトのa01やa02はundefinedではなくなります。

続いて、コンストラクタPersonsのlogメソッドですが、以下のようにして実行してみましょ。

  Persons.prototype.log = function() {
    Object.keys(this).forEach(
      function(id) {
        Logger.log(this[id]);
      }
    );  
  }

関数内のthisはグローバルオブジェクトを表す

確かに、forEachメソッドの関数内のthisはグローバルオブジェクトでしたね。

selfを使ってインスタンスを関数内に持ち込む

では、forEachメソッドの関数内でインスタンスを参照したいときはどうすれば良いでしょうか?

実はそんなに難しいことではなくて、logメソッド内でインスタンスを指しているときのthisを任意の変数に代入しておいて、関数内からそれを参照すればOKです。

その任意の変数として、よくselfという変数名が使われるというわけです。

つまり、こういうことです。

  Persons.prototype.log = function() {
    var self = this;
    Object.keys(this).forEach(
      function(id) {
        self[id].log();
      }
    );  
  }

logメソッド内では、thisは呼び出したオブジェクトを指します。つまり、Personsオブジェクトのインスタンスです。

それを、変数selfに代入しておきます。

forEachメソッド内ではthisはグローバルオブジェクトになってしまいますが、変数selfは変わらずPersonsオブジェクトのインスタンスを参照しているというわけです。

myFunctionを実行すると、無事にすべてのデータについてログ出力されていることを確認できます。

Personsクラスのインスタンスのデータをログ出力

まとめ

以上、GASでselfを使って関数の中からインスタンスを表すthisを使えるようにする方法をお伝えしました。

スコープとthis…ものすごいJavaScriptの特徴を味わえる内容だったと思います。

けっこうハマりやすいポイントだと思いますので、覚えておくとよいですね。

では、次回はいよいよPersonsオブジェクトの内容をスプレッドシートに反映させるメソッドを作ります。

GASでオブジェクトのデータをスプレッドシートに反映させるメソッドの作り方
「初心者でもわかるGoogle Apps Scriptのクラス」ということでシリーズをお送りしております。GASでオブジェクトのデータをスプレッドシートに反映させるメソッドの作り方をお伝えします。

どうぞお楽しみに!

連載目次:初心者向けGoogle Apps Scriptでクラスを作ろう

使いどころやそのメリットが分かりづらいGASの「クラス」。本シリーズでは、初心者でもわかるように「これでもか!」とじっくり着実にクラスとそのメリットについて解説をしていきます。
  1. 【初心者向け】Google Apps Scriptでクラスを理解するためのオブジェクトの基礎知識
  2. 初心者でもできるGoogle Apps Scriptで最も簡単なクラスを作る方法
  3. Google Apps Scriptでクラスに最も簡単なプロパティを追加する方法
  4. Google Apps Scriptでクラスに最も簡単なメソッドを追加する方法
  5. Google Apps Scriptでスプレッドシートのデータの1行分を表すクラスを作る方法
  6. GASでゲッターを使って簡単なプロパティを作成する方法
  7. GASでセッターを使ってプロパティの入力に制限をかける

タイトルとURLをコピーしました