特集
» 2015年11月06日 05時00分 公開

特集:C#×JavaScript:C#開発者のための最新JavaScript事情(関数編) (2/3)

[かわさきしんじ,Insider.NET編集部]

 前回のクラス定義の際に取り扱ったのであまり説明することはないのだが、いくつか補足的な事項もある。

クラスの静的メソッド

 まずは最初に見たC#コードを、それと同様な構造を持つように書き直しておこう。まずはJavaScript 5から。匿名関数の即実行により、変数Programにコンストラクター関数Programが設定されているのが分かる(ただし、ここではProgramクラスのオブジェクトは生成していない)。また、二つの静的メソッドも匿名関数を使って、Programオブジェクトのプロパティとして設定されている(このような場合に関数宣言を使って、静的メソッドを定義はできない)。

var Program = (function() {
  function Program() {};
  Program.Hello = function(s) {
    console.log("hello " + s);
  };
  Program.Main = function() {
    Program.Hello("JavaScript");
  };
  return Program;
})();

Program.Main();


 ここで重要なのは、静的メソッドHelloの呼び出し方だ。C#のコードでは単に「Hello("...")」としていたが、JavaScriptでは「クラス名.静的メソッド名(パラメーター)」の形で呼び出す必要がある。これはJavaScriptでクラスベース(的な)オブジェクト指向プログラミングを行う場合、クラスの静的メソッドはコンストラクター関数(ここではProgramオブジェクト)のプロパティとして実装されるからだ。

クラスの静的メソッド クラスの静的メソッド

 C#であれば、あるクラスのメソッド(静的メソッド/インスタンスメソッド)から同じクラスの静的メソッドを呼び出すのにクラス名を付加する必要はなかった。しかし、JavaScriptでは、上の図にあるようにインスタンスメソッドの検索経路(プロトタイプチェーン)上には静的メソッドは存在しない。また、あるクラスの静的メソッドから同じクラスの静的メソッドを呼び出す場合にも、そのメソッドは見えないのでクラス名で修飾してやる必要があるのだ。

 このことは、ECMAScript 2015でもTypeScriptでも変わらない。

class Program {
  constructor() {}
  static Hello(s) {
    console.log("hello " + s);
  }
  static Main() {
    Program.Hello("ECMAScript 2015");
  }
}

Program.Main();

class Program {
  constructor() {}
  static Hello(s: string): void {
    alert("hello " + s);
  }
  static Main():void {
    Program.Hello("TypeScript");
  }
}

Program.Main();

ECMAScript 2015/TypeScriptで書き換えたもの
上はECMAScript 2015バージョン
下はTypeScriptバージョン

 どちらのコードでもクラス名を静的メソッドの前に指定する必要がある。

 また、JavaScript 5バージョンのコードでは匿名関数を使っていたが、ECMAScript 2015/TypeScriptではメソッド宣言を関数宣言と同じ形式で行っているのが分かる。ただし、正式にはこれは、ECMAScript 2015ではメソッド定義、TypeScriptではメンバー関数宣言と呼ばれる。静的メソッドを定義する構文は次のようになっている(概要)。

class クラス名 {
  static プロパティ名 (パラメーターリスト) { 関数本体 }
  ……
}

class クラス名 {
  アクセス修飾子 static プロパティ名<型パラメーター> (パラメーターリスト) :戻り値型 { 関数本体 }
  ……
}

ECMAScript 2015/TypeScriptで静的メソッドを定義する構文
TypeScriptではアクセス修飾子/型パラメーター/戻り値型は省略可能。上のコードはアクセス修飾子と型パラメーターを省略している。

 なお、前回も述べたが、ECMAScript 2015ではクラス定義内にフィールド(メンバー変数)の宣言は記述できない。コンストラクターの中で直接、必要なメンバーの初期値を設定していく。これに対して、TypeScriptではこれらを宣言したり、コンストラクターのパラメーターリストを使用したりして、それらを初期化できる。

インスタンスメソッド

 インスタンスメソッドは、ご存じの通り、あるクラスのインスタンスを介して呼び出し可能なメソッドだ。

 JavaScript 5だと、C#開発者にはちょっと奇妙なインスタンスメソッドが定義できる。ということは、ECMAScript 2015/TypeScriptでも同じことができるということだ。なお、前回も述べたが、JavaScriptでは自身のオブジェクトのプロパティ(メンバー)を参照するのに「this」が必須である。

var Foo = (function() {
  function Foo(lang) {
    this.lang = lang;
    this.SeeYou = function() {
      console.log("see you " + this.lang);
    };
  };
  Foo.prototype.Hello = function() {
    console.log("hello " + this.lang);
  }
  return Foo;
})();

var f = new Foo("JavaScript");
f.Hello();
f.SeeYou();

2種類のインスタンスメソッド
強調書体で表示されている部分に注目されたい。

 通常、インスタンスメソッドとして考えられているのは、関数Fooのprototypeプロパティに追加されているHello関数だ。これは、「new Foo()」として生成されたオブジェクト全てが共有し、プロトタイプチェーンをたどることで呼び出される。また、「new」と共にコンストラクター関数を呼び出すと、インスタンスが新たに生成され、それがコンストラクターに渡される。よって、この場合はFoo関数内ではthisが新たに生成されたインスタンスを参照し、これにより「this.〜」が新規に生成されたオブジェクトのプロパティを参照することになる(この場合はプロパティとメソッドが追加される)。

 コンストラクター関数Fooの中ではプロパティ(フィールド/メンバー変数)に加えて「this.SeeYou = function() { ...}」として「個々のインスタンスが所有する(=プロパティとして持つ)関数」(インスタンスメソッド)が定義されている。全てのインスタンスが別個にこの関数を所有するためにメモリ使用量の面で不利になるので、通常はこのようなことはしないだろう。ただし、コンストラクター内部でこのようなインスタンスごとにメソッドを持たせるのではなく、何らかの理由から後付けで特定のインスタンスにメソッドを追加できることは覚えておいてもよいかもしれない。

2種類のインスタンスメソッド 2種類のインスタンスメソッド

 同様なコードをあえてC#で書くと、次のようになるだろう(する必要があるかどうかは別にして)。ここでは、Actionデリゲートとラムダ式を組み合わせている。

namespace cs
{
  class Foo
  {
    private string lang;
    public Action SeeYou;

    public Foo(string lang)
    {
      this.lang = lang;
      this.SeeYou = () => Console.WriteLine("See You " + this.lang);
    }

    public void Hello()
    {
      Console.WriteLine("hello " + this.lang);
    }
  }

  class Program
  {
    public static void Main()
    {
      var f = new Foo("C#");
      f.Hello();
      f.SeeYou();

      Console.ReadKey();
    }
  }
}

インスタンスごとにメソッドを持つC#コード

 ECMAScript 2015/TypeScriptのコードも示しておこう。最初のJavaScript 5コードと同様にコンストラクター内部で匿名関数を利用して、個々のインスタンスが所有するメソッドを定義できている。

class Foo {
  constructor(lang) {
    this.lang = lang;
    this.SeeYou = function() {
      console.log("See You " + this.lang);
    }
  }
  Hello() {
    console.log("hello " + this.lang);
  }
}

var f = new Foo("ECMAScript 2015");
f.Hello();
f.SeeYou();

class Foo {
  private lang: string;
  public SeeYou: Function
  constructor(lang: string) {
    this.lang = lang;
    this.SeeYou = function(): void {
      alert("See You " + this.lang);
    }
  }
  Hello(): void {
    alert("hello " + this.lang);
  }
}

var f = new Foo("TypeScript");
f.Hello();
f.SeeYou();

同じことをするECMAScript 2015/TypeScriptのコード
上はECMAScript 2015バージョン
下はTypeScriptバージョン

 次にクロージャの記述を見てみよう。

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。