JS.next

JavaScriptの最新実装情報を追うブログ

superキーワードが実装されてきている

概要

長らく予約語であったsuperキーワードが、継承元の関数を呼ぶためのキーワードとして機能するようになった。


2種類の使い方

superキーワードは場所によって2種類の使い方があり、それぞれ違う振る舞いをする。
1つ目はコンストラクタ内で使う場合、2つ目はメソッド内で使う場合である。


コンストラクタ内で使う場合

ここでいうコンストラクタとは、functionキーワードを用いた関数定義と、Class構文中のconstructorメソッドのことである。
この場合、「super()」や「new super」などとすることにより、その関数のプロトタイプである関数を呼ぶことができる。
その際、呼び出し先の「this」は呼び出し元の「this」に設定されて呼ばれる。(「new」を付けない場合)

つまり、Animalコンストラクタを継承したCatコンストラクタを定義したい以下の例だと、

Cat.__proto__ = Animal
function Animal(name) { this.name = name }
function Cat(name) { Animal.call(this, name) }  // ※注目

var tama = new Cat('タマ')
console.log( tama.name )  // "タマ" 

Cat.__proto__ = Animal
function Animal(name) { this.name = name }
function Cat(name) { super(name) }  // ※注目

var tama = new Cat('タマ')
console.log( tama.name )  // "タマ" 

と書ける。
Catコンストラクタ内の「super()」はCatのプロトタイプであるAnimalを、thisを引き継いで呼ぶことになる。


メソッド内で使う場合 (※実装不十分 詳しくは記事末へ)

ここでいうメソッドとは、メソッド定義記法やClass構文中で定義されている関数のことである。
この場合、まずメソッドが定義される時に、各メソッドにそれが属するオブジェクトが『[[HomeObject]]』として紐付けされる。
属するオブジェクトとは、メソッド定義記法なら『定義されるオブジェクト』、Class構文中の一般メソッドであれば『クラスのprototypeオブジェクト』、Class構文中のstaticメソッドであれば『クラスオブジェクト』となる。
そしてメソッド内で「super.foo()」や「super['foo']()」などとすると、その[[HomeObject]]のプロトタイプの「foo」メソッドを呼ぶことができる。
その際、呼び出し先の「this」は呼び出し元の「this」に設定されて呼ばれる。(「new」を付けない場合)

つまり、Animalのprototypeオブジェクトを継承したCatのprototypeオブジェクトを定義したい以下のケースだと、

function Animal() { }
Animal.prototype = {
  speak(cry) { console.log( cry ) }
}

function Cat() { }
Cat.prototype = { __proto__: Animal.prototype,
  meow() { Animal.prototype.speak.call( this, 'ミャオ' ) }  // ※注目
}

var tama = new Cat()
tama.meow()  // "ミャオ" 

function Animal() { }
Animal.prototype = {
  speak(cry) { console.log( cry ) }
}

function Cat() { }
Cat.prototype = { __proto__: Animal.prototype,
  meow() { super.speak( 'ミャオ' ) }  // ※注目
}

var tama = new Cat()
tama.meow()  // "ミャオ" 

と書ける。
meowメソッド内の「super.speak()」はmeowの[[HomeObject]]であるCat.prototypeのプロトタイプであるAnimal.prototypeのspeakメソッドを、thisを引き継いで呼ぶことになる。


2種類を合わせると (※実装不十分 詳しくは記事末へ)

Animalクラスを継承したCatクラスを定義したい以下の例だと、

function Animal(name) { this.name = name }
Animal.prototype = {
  speak(cry) { console.log( this.name+ 'は' +cry+ 'と鳴きました' ) }
}

Cat.__proto__ = Animal
function Cat(name) { Animal.call(this, name) }  // ※注目
Cat.prototype = {
  __proto__: Animal.prototype,
  meow() { Animal.prototype.speak.call( this, 'ミャオ' ) }  // ※注目
}

var tama = new Cat('タマ')
tama.meow()  // "タマはミャオと鳴きました" 

function Animal(name) { this.name = name }
Animal.prototype = {
  speak(cry) { console.log( this.name+ 'は' +cry+ 'と鳴きました' ) }
}

Cat.__proto__ = Animal
function Cat(name) { super(name) }  // ※注目
Cat.prototype = {
  __proto__: Animal.prototype,
  meow() { super.speak( 'ミャオ' ) }  // ※注目
}

var tama = new Cat('タマ')
tama.meow()  // "タマはミャオと鳴きました" 

と書ける。


[[HomeObject]]を明示的に指定する

『Function.prototype.toMethod(homeObject)』メソッドを呼ぶと、[[HomeObject]]を設定した新しい関数を返してくれる。
あくまで新しい関数であり、元の関数の[[HomeObject]]が変更されるわけではないことに注意。

var obj = {
  __proto__: {
    superMethod: function () { return 'super!!' }
  },
  method: function () { return super.superMethod() }
}

var objsMethod = obj.method.toMethod(obj)
  // 「obj.method」の機能を引き継いで、新しく[[HomeObject]]がobjに設定された関数を作る

console.log( objsMethod == obj.method )  // false
  // toMethodは元の関数には影響を与えず、返される関数は新しいものである

obj.method = objsMethod
  // これで「obj.method」内でsuperキーワードが正常に機能するようになる。

console.log( obj.method() )  // "super!!"


実装不十分解消パッチ

現在のV8ではメソッド定義記法において[[HomeObject]]を自動的に設定してくれないので、上記の例のいくつかはそのままではうまく動かない。
そのためメソッドが定義されたオブジェクトやコンストラクタを渡すと[[HomeObject]]を設定してくれる関数を用意した。(アクセサメソッド等はサポートしていない簡易的なもの)
V8がサポートするまでの間サンプルを動かすために利用して欲しい。

function fix(obj) {
  Object.keys(obj).forEach( k => {
    if (typeof obj[k] == 'function') obj[k] = obj[k].toMethod(obj)
  } )
  if (typeof obj.prototype == 'object') fix(obj.prototype)
}


実装されるバージョン

V8 ~3.30.18(ひと通りの振る舞い) 3.30.22(Class構文での[[HomeObject]]の自動設定)
メソッド定義記法での[[HomeObject]]の自動設定はまだ)