super キーワード

ECMAScript 6th には super キーワードがある。もとからキーワードとしてはあったけど、6th から挙動をもつキーワードとなった。

期待通り、プロトタイプチェーン上のプロパティアクセスまたはメソッドコールに使用できるキーワードである。

使用可能な箇所は:

  • MethoDefinition中
    • (普通の)メソッド
    • getter
    • setter

MethodDefinitionというのは

({
  method () {
  },
  get foo () {
  },
  set foo (val) {
  }
})

というように、ECMAScript6thから登場する書き方である。

super の使用方法は2種類ほどある。

  1. プロパティアクセス:継承先のプロパティへアクセス
    • super [ Expression ]
    • super . IdentifierName
  2. コンストラクタ/メソッド実行:継承先の同名のメソッド/関数を実行
    • new super Argumentsopt
    • super Arguments

super単体にアクセスする構文はないことに注意。

class Foo {
  constructor (name) {
    this.greeting = "Hello";
    this.name = name;
  }
  getClassName () {
    return "Foo";
  }
}
class Bar extends Foo {
  constructor (name) {
    super(name);
  }
  getClassName () {
    return "Bar[" + super() + "]";
  }
  sayGreeting () {
    console.log(this.greeting + " " + this.getClassName());
  }
}

super から参照できるオブジェクトは静的

以下のケースを考えてみる

var bar = new Bar("aaa");
var getClassName = bar.getClassName;
getClassName();

実はこれ、きちんと "Bar[Foo]" が返る(はず)

this のケースだったら実行のされ方によって変化しうるので想定通りには動かないのだが、superの場合はそうはならない。

内部的にはメソッド定義時に内部で super が使用されている場合、そのメソッドの内部プロパティ[[HomeObject]]に親オブジェクトが設置される。BarクラスのgetClassNameであれば、Bar.prototypeが設置されるわけだ。
そして super() は、そのメソッドにある[[HomeObject]]からプロトタイプチェーン上の一つ上へ上がったオブジェクトの同名のメソッドを取得して実行する。

この内部プロパティはECMAScript6thになって新設されたプロパティだ。上記 super の仕組を実現するために存在している。
また、[[HomeObject]]は一部の例外を除き再設置することはできない。

superの動きを this を使ってエミュレートするのは難しい

最初のコード例で行くと、super の仕組は this を使って実現できる。

class Bar extends Foo {
  getClassName () {
    return Object.getPrototypeOf(Object.getPrototypeOf(this)).getClassName.call(this);
    // return this.__proto__.__proto__.getClassName.call(this);
  }
}

しかし、これは多段の継承をしていくと破綻する。(これまでのJavaScriptでクラスを作ろうとした人はこの問題に一度は直面した経験があるだろう)

class ExBar extends Bar {
  getClassName () {
    return Object.getPrototypeOf(Object.getPrototypeOf(this)).getClassName.call(this);
  }
}

さらにBarを継承したExBarを作って、そのインスタンスから getClassName() を呼んでみよう。ExBar の getClassName は正しくBarのgetClassNameを呼び出すだろう。しかし、BarのgetClassNameはFooのgetClassNameを呼び出すことはできず、再度BarのgetClassNameを呼び出す結果となってしまう("too much recursion"などの例外が発生するだろう)

[[HomeObject]]を使ったsuperでは、メソッド定義時に設置された親オブジェクトから一つ上の[[Prototype]]を参照すれば良く、安定して使えるわけだ。

制限

ECMAScript6thになって、Functionオブジェクトの生成方法が増えている

  1. function
    • FunctionDeclaration
    • FunctionExpression
  2. MethodDefinition
  3. ArrowFunction

この内、FunctionDeclaration はオブジェクトのプロパティとして宣言できないので、実質3つの書き方があるわけだ。

だが、これまでsuperの説明にMethodDefinitionを使用してきたのにはわけがあって、[[HomeObject]]が設定されるのは、このMethodDefinitionのみなのだ。

{ foo: function () { } }といったオブジェクトリテラル上にFunctionExpressionを使用する従来のやり方では[[HomeObject]]はセットされない。
また、ArrowFunctionは、this と同様 super もレキシカルに解決するために、こちらも[[HomeObject]]はセットされない。(参照:ES.next Draft仕様より、普通のfunctionとArrowFunctionの違い - hogehoge)

まぁ、super を使うなら、class構文で定義したメソッドだろうし、class構文では MethoDefinition しか使えないわけで、あまり気にすることはないと思うけどね。

以上、自分が仕様を読んで super について把握したもの、まとめでした。