CoffeeScriptでクラスを作り、コンパイル後のjsファイルを読み解く


CoffeeScriptで手続き的に記述していたプログラムを抽象的にするために、いくつか関数化を試みていました。ですが、関数名に悩むのでクラスにすることに。けど、JavaScriptでのクラスも経験が全然ないので、CoffeeScriptをコンパイルしてできたjsファイルを読み込んでみました。とりあえず、これだけわかれば作業には取り掛かれます。

class Person

コンパイルすると次のとおりになります。

(function() {
  var Person;

  Person = (function() {
    function Person() {}

    return Person;

  })();

}).call(this);

全体を覆う無名関数

全体を無名関数で囲っています。これは何をしているのでしょうか?

(function() {
}).call(this);

次のコードを実行するとWindowと表示されます。無名関数の外でthisを表示してもWindowと表示されます。無名関数にcallを使うことによって、無名関数の中のthisをグローバルオブジェクトと結びつけているのです。もしグローバルオブジェクトがWindowじゃない場合にも対応できます。もしくはグローバルオブジェクトではなく、また何かの関数の中で使われるかもしれませんね。

console.log(this);
(function() {
  console.log(this);
}).call(this);

// ->
// Window {top: Window,…}
// Window {top: Window,…}

上記はcallの使う目的についてでした。では、無名関数自体は何に使われているのでしょうか?その答えは次のコードでわかります。

var hoge = 'outer_hoge';
(function() {
  var hoge = 'inner_hoge';
}).call(this);
console.log(hoge);

// -> outer_hoge

JavaScriptは関数によって新しい内側のスコープが作成されます。なので無名関数の外と中では同じ変数名でも別々のを指すわけです。ただし、これは無名関数で最初に変数を使う際にvarが必須です。varがないと変数の宣言にならずに外側のスコープを見に行きます。(厳密にはローカルスコープの宣言にはならないがグローバルスコープの宣言にはなる。)

全体を無名関数でラッピングすることによって、ライブラリを作ったり、複数人で作業する際にお互いが同じ変数名を使っていないか気にする必要がなくなるということです。隠蔽するわけですね。

classには即時実行関数が使われる

class Person
  name = "taro"
  console.log "innser Person class"

classを使うと即時実行関数がクラス名の変数に代入されています。この関数はクラス名の関数オブジェクトを返すのですが、一緒にnameとconsole.logというのは変数Personには格納されません。しかし、実行はされます。これを利用することでクラス読込時に準備の処理を実行させることができます。他にも色々な応用があるでしょう。

(function() {
  var Person;

  Person = (function() {
    var name;

    function Person() {}

    name = "taro";

    console.log("innser Person class");

    return Person;

  })();

}).call(this);

thisを表す@

class Person
  name = "taro"
  @age = 17

  constructor: (sex)->
    @sex = sex

CoffeeScriptではthisは@と書くことが出来ます。@agethis.ageと同じです。constructorの中で使うとインスタンス変数。外で使うとクラス変数として扱われます。

(function() {
  var Person;

  Person = (function() {
    var name;

    name = "taro";

    Person.age = 17;

    function Person(sex) {
      this.sex = sex;
    }

    return Person;

  })();

}).call(this);