classの継承についての考察
概要
ES6の継承が遅すぎると話題なので検証・最適化をしました。
どれくらい遅いのか
こちらのベンチマークによると、少しずつ改善は見られるものの、2.3-17倍程度遅い様子です。
実際に検証しました。
- Node v6.10.1
- benchmark.js v2.1.4
継承
// classes function ParentES5(value) { this._value = value; } function ES5(value) { ParentES5.call(this, value); } util.inherits(ES5, ParentES5); class ParentES6 { constructor(value) { this._value = value; } } class ES6 extends ParentES6 { constructor(value) { super(value); } } // functions const funcs = { 'es5': function() { return new ES5(1); }, 'es6': function() { return new ES6(1); } };
**** benchmark.js **** [1] "es5" 0.000029ms [1.00] [2] "es6" 0.000045ms [1.56]
単純にclassを使うだけでも1.5倍遅いようです。
super classメソッドの呼び出し
function ParentES5(value) { this._value = value; } ParentES5.prototype.get = function() { return this._value; }; function ES5(value) { ParentES5.call(this, value); } util.inherits(ES5, ParentES5); ES5.prototype.get = function() { return ParentES5.prototype.get.call(this) * 2; }; class ParentES6 { constructor(value) { this._value = value; } get() { return this._value; } } class ES6 extends ParentES6 { constructor(value) { super(value); } get() { return super.get() * 2; } } const es5 = new ES5(1); const es6 = new ES6(1); // functions const funcs = { 'es5': function() { return es5.get(); }, 'es6': function() { return es6.get(); } }
**** benchmark.js **** [1] "es5" 0.000021ms [1.00] [2] "es6" 0.000078ms [3.79]
super classメソッドの呼び出しは非常に遅いので避けたいところです。
考察
super classメソッドの呼び出し
function ParentES5(value) { this._value = value; } ParentES5.prototype.get = function() { return this._value; }; function ES5(value) { ParentES5.call(this, value); } util.inherits(ES5, ParentES5); ES5.prototype.get = function() { return ParentES5.prototype.get.call(this) * 2; }; class ParentES6 { constructor(value) { this._value = value; } get() { return this._value; } } class ES6 extends ParentES6 { constructor(value) { super(value); } get() { return ParentES6.prototype.get.call(this) * 2; } } const es5 = new ES5(1); const es6 = new ES6(1); // functions const funcs = { 'es5': function() { return es5.get(); }, 'es6': function() { return es6.get(); } }
**** benchmark.js **** [1] "es6" 0.000020ms [1.00] [2] "es5" 0.000020ms [1.02]
super.get()
の代わりにsuper classメソッドをcall
することでes5と同等のパフォーマンスが得られました。これは使えそうです。
こちらのパターンも同じような結果が得られます。
function ParentES5(value) { this._value = value; } function getES5() { return this._value; }; function ES5(value) { ParentES5.call(this, value); } util.inherits(ES5, ParentES5); ES5.prototype.get = function() { return getES5.call(this) * 2; }; class ParentES6 { constructor(value) { this._value = value; } } function getES6() { return this._value; }; class ES6 extends ParentES6 { constructor(value) { super(value); } get() { return getES6.call(this) * 2; } } const es5 = new ES5(1); const es6 = new ES6(1); // functions const funcs = { 'es5': function() { return es5.get(); }, 'es6': function() { return es6.get(); } }
**** benchmark.js **** [1] "es5" 0.000023ms [1.00] [2] "es6" 0.000023ms [1.00]
こちらもなかなか使いどころがありそうです。
継承
ParentES6.prototype.constructor
へのアクセスは禁止されているため、classをextendsしsuperを呼び出す必要があります。
class ParentES6 { constructor(value) { this._value = value; } } class ES6 extends ParentES6 { constructor(value) { super(value); } } // エラー class ES6 { constructor(value) { ParentES6.prototype.constructor.call(this, value); } } // TypeError: Class constructor ParentES6 cannot be invoked without 'new'
今のところたどり着いた最速の継承はこちらです。
function ParentES5(value) { this._value = value; } function ES5(value) { ParentES5.call(this, value); } util.inherits(ES5, ParentES5); class ParentES6 { constructor(value) { this._value = value; } get() { return this._value; } } function ES6(value) { this._value = value; } ES6.prototype = ParentES6.prototype; const es5 = new ES5(1); const es6 = new ES6(1); console.log(es6.get()); // 1 console.log(es6 instanceof ParentES6); // true // functions const funcs = { 'es5': function() { return es5.get(); }, 'es6': function() { return es6.get(); } }
[1] "es6" 0.000020ms [1.00] [2] "es5" 0.000026ms [1.33]
es5の継承よりも高速な継承が可能となりました。もう少し検証が必要ですが、instanceofがtrueとみなされるのでこちらは意外と使えるかもしれません。 こちらの案はthamminさんがベースを提供してくれました。
まとめ
superクラスの呼び出しを避ける考察でした。ns,μsレベルのチューニングなので普通にsuperを呼んでいただければと思います。
JavaScriptっておもしろい。