老害と言われないためのES6勉強会: class編

  • 56
    いいね
  • 0
    コメント

前回


ECMA2015になることによって、いわゆる良く見るプログラミング言語のクラス定義ができるようになる。

class People {
    constructor(name) {
        this.name = name; 
    }

    greeting() {
        console.log(`Hello, ${this.name} !`);
    }
}

こう書くことによって

new People("esehara").greeting()

というように書くことができる。


ECMA5.1の時代にはどうしてた?

classは元々予約語だったので使えなかったので、klassみたいなものを使うことが多かった。例えば、『ステートフルJavaScript』では、クラスのような定義に関しては、次のような風に、クラス風の構造を作ることができる:

var Class = function() {
    var klass = function () {
        this.init.apply(this, arguments);
    };
    klass.prototype.init = function() {};
    return klass
};

var Person = new Class();

Person.prototype.init = function(name) {
    this.name = name;
};

Person.prototype.greeting = function() {
  console.log("Hello, " + this.name + "!");
};

var esehara = new Person("esehara");
esehara.greeting();

何やってんの

要するに、JavaScriptにおいてクラスを実装するさいには、関数によって実装されていると見なしてもいい。関数もまたオブジェクトであり、prototypeによって、その関数のプロパティに対して、メソッドとして、関数を値として代入する。これを省略する方法がclassの構文であると見做すことができる。


???

JavaScriptのオブジェクト指向というのは、基本的にプロトタイプべースである。そして、Objectを継承することになる。関数も同様にオブジェクトである。例えば、関数であるならば、推奨はされていないけれども、new Function()で関数を作ることが可能である。JavaScriptでオブジェクトであるということは、そのオブジェクトに対してパラメーターを表示することを意味している。


無名Class

ちなみに、名前のないClassも作ることが可能。例えば:

var anonymous = class {}

といったような宣言も可能。なので、クロージャを利用したclassを作る関数も可能である。

function greetingClass(greet) {
  return class {
      constructor(name) {
          this.name = name;
        }
        greeting() {
          console.log(`${this.name}さん、${greet}`);
        }
    }
}
var goodmoning = greetingClass("おはよう");
new goodmoning("esehara").greeting();
new goodmoning("Qiita").greeting();

継承もできるようになりました

class People 
    constructor(name) {
        this.name = name
    }
}

class Engineer extends People {
  work() {
      console.log(`${this.name}がプログラミングしています。`);
    }
}

new Engineer("esehara").work();

ただし、JavaScriptの場合の継承は単一継承、つまり二つのクラスを同時に継承することができない。そうすると、例えばある関心事毎に継承したい場合、困る。


関心毎に継承する???

JavaScriptのES2015クラスでmixinを実装する世界一美しい方法から引用:

class Swimmable{
    swim(){
        console.log("すいすいっ");
    }
}

class Flyable{
    fly(){
        console.log("ぴょんぴょんっ");
    }
}

class Fish extends Swimmable{}

// ※トビウオは英語で「flying fish」
class FlyingFish extends Fish{} // ←Flyableが継承できねえ…

const flyingFish = new FlyingFish();
flyingFish.swim(); // "すいすいっ"
flyingFish.fly();  // ←そんなメソッドねーよ!!!

この場合、なんらかの動物に対しての関心事(飛べる、泳げる)というかたちでクラスを分割しているわけだけれども、単一継承の場合、「飛べる かつ 泳げる」という形で表現できない。もしこれを表現したい場合は、「飛べる かつ 泳げる」というクラルを別途用意しなければいけなくなるのだけれども、それは明かに冗長。


Mixin

とすると、別途関心事に分けて、それを使えるようにするような何かが欲しいということになる。それが「Mixin」ということになる。上記の記事では実装をしていたけれども、実際には仕様として存在している。先の「飛べる」「泳げる」という関心をMixinで表現すると、次のようになる。

var Swimmable = Base => class extends Base {
    swim(){
        console.log("すいすいっ");
    }
};

var Flyable = Base => class extends Base {
    fly(){
        console.log("ぴょんぴょんっ");
    }
};
class Fish extends Swimmable(class {}) {};
class FlyingFish extends Flyable(Fish) {};
var fish = new FlyingFish();
fish.fly();
fish.swim();

しかしこれはダサい

例えば"Real" Mixins with JavaScript Classesでは、Dartのように、class B extends A with M {}と書けたほうが綺麗なのであって、classを関数のように渡していくスタイルというのは、どうしてもみばえが悪い。なので、

class MixinBuilder {  
  constructor(superclass) {
    this.superclass = superclass;
  }

  with(...mixins) { 
    return mixins.reduce((c, mixin) => mixin(c), this.superclass);
  }
}

ポイントは、Mixinに関しても、恰も関数のように扱えるインターフェイスが整っているということだった。従って、reduceを使って、引数のリスト(引数のリストは、...の構文で取得できる)で渡ってきたMixinを関数みたいに適用していけばいいということになる。ただし、ここまでやると若干やりすぎな感じもしなくはない。