軈てmoduleやclassが使へるやうに成れば此の記事は無駄に成る。さっさと無駄に成りたい。
_extends()を書いた。JavaScriptで継承をする実装は世に星の数程有る訳だが、実はcodeを読んだ事が無い。継承が好きではなく殆どやらないし、やるにしてもJavaScriptの標準的な書き方で、其々のconstructorに三行書き足せば実装できる。例へば以下の樣にだ。
console.log('\nSuper'); function Super(a, b){ this.a = a; this.b = b; } Super.prototype.name = function () { return 'Super ' + this.a; }; o = new Super(2, 3); console.log([o instanceof Super]); console.log(o); console.log([o.name(), o.toString()]);
console.log('\nSub normal'); function Subn(a, b) { Super.call(this, a, b); // 1 this.c = 4; } Subn.prototype = Object.create(Super.prototype); // 2 Subn.prototype.constructor = Subn; // 3 Subn.prototype.tool = function () { return 'Tool ' + this.b + ' ' + this.c; }; o = new Subn(2, 3); console.log([o instanceof Super, o instanceof Subn]); console.log(o); console.log([o.name(), o.tool(), o.toString()]);
SubnはSuperを継承してゐる。三行書き足す丈だ。commentに1, 2, 3と書いた行だ。
本質的な行だけ抜き出すと、
function Super(a, b) { } function Subn(a, b) { Super.call(this, a, b); // 1 } Subn.prototype = Object.create(Super.prototype); // 2 Subn.prototype.constructor = Subn; // 3
である。Subnのconsole.logは
Sub normal [ true, true ] { a: 2, b: 3, c: 4 } [ 'Super 2', 'Tool 3 4', '[object Object]' ]
と表示する。以下此の出力を基準とし、此れと一致すれば継承出来てゐると見做す。
世に星の数程有る_extends()の実装には、protptype
と云ふ記述を排除しやうとしてゐたり (var Sub = _extends(Super, { constructor: function () {}, prop1: 'prop1', method1: function () {}, method2: function () {} });
のやうな類い)、余計なお世話だ。或いは単に私が信用を感じないと云ふ理由で、一回も使った事が無いし、実装も読まなかった。
「普通の」_extends()を実装する
ただjavaScriptの普通の記法にてconstructorに三行書き足す丈で継承は実現できるのだった。此れを数文字に短縮する為に、ただ其れと同じ働きのみをし、JavaScriptの脳を邪魔しない「普通の」_extends()を実装する。
Array.from()
は以下の関数とする。ES6に入ってる筈だけど。
if (!Array.from) { // ES6 Array.from = function (obj) { return [].slice.call(obj); }; }
_extends()の実装を示す。
// License: Public Domain /** * @param {function(Object...):Object} _super * @param {function(Object...):Object} _constructor * @return {function(Object...):Object} */ function _extends(_super, _constructor) { var _class = null, toString; _class = function() { if (!(this instanceof _class)) { return new (Function.prototype.bind.apply(_class, [ null ].concat(Array.from(arguments)))); } _super.apply(this, arguments); return _constructor.apply(this, arguments); }; _class.prototype = Object.create(_super.prototype); _class.prototype.constructor = _class; toString = Function.prototype.toString; Function.prototype.toString = function () { if (this === _class) { return _constructor.toString(); } return toString.call(this); }; return _class; }
newを強制するpatternは組み込んである。prototype chainのpatternとも言ふ。
cf. prototype chainのpattern (JavaScript) http://c4se.hatenablog.com/entry/20110323/1300824873
toString()を組んであるのは、AngularJS等のinjection libraryでFunction.prototype.toString()を使ってゐる例が有る為。Rubyを感じる(((〃l _ l)))
さて、此の_extends()がJavaScriptの普通の継承と本当に等しいものであるか、実験してみる。
console.log('\nSub extended'); var Sube = _extends(Super, function (a, b) { this.c = 4; }); Sube.prototype.tool = function () { return 'Tool ' + this.b + ' ' + this.c; }; o = new Sube(2, 3); console.log([o instanceof Super, o instanceof Sube]); console.log(o); console.log([o.name(), o.tool(), o.toString()]); console.log('\nSub extended without new'); o = Sube(2, 3); console.log([o instanceof Super, o instanceof Sube]); console.log(o); console.log([o.name(), o.tool(), o.toString()]);
Superはさっきと同じものだ。同等の出力が得られる。
Sub extended [ true, true ] { a: 2, b: 3, c: 4 } [ 'Super 2', 'Tool 3 4', '[object Object]' ] Sub extended without new [ true, true ] { a: 2, b: 3, c: 4 } [ 'Super 2', 'Tool 3 4', '[object Object]' ]
好い。
Singletonも組み込んでみる。
cf. JavaScriptでsingletonを作る http://c4se.hatenablog.com/entry/2013/09/09/124309
先ずJavaScriptの普通の記法に依るSingletonを示す。当然Superを継承させる。
console.log('\nSingleton normal 1'); function Singln1(a, b) { if (Singln1.instance) { return Singln1.instance; } Singln1.instance = this; Super.call(this, a, b); this.c = 4; } Singln1.instance = null; Singln1.prototype = Object.create(Super.prototype); Singln1.prototype.constructor = Singln1; Singln1.prototype.tool = function () { return 'Tool ' + this.b + ' ' + this.c; }; o = new Singln1(2, 3); console.log([o instanceof Super, o instanceof Singln1]); console.log(o); console.log([o.name(), o.tool(), o.toString()]); console.log(o === new Singln1());
console.log('\nSingleton normal 2'); function Singln2(a, b) { var me = this; Singln2 = function () { return me; } Super.call(this, a, b); this.c = 4; } Singln2.prototype = Object.create(Super.prototype); Singln2.prototype.constructor = Singln2; Singln2.prototype.tool = function () { return 'Tool ' + this.b + ' ' + this.c; }; o = new Singln2(2, 3); console.log([o instanceof Super, o instanceof Singln2]); console.log(o); console.log([o.name(), o.tool(), o.toString()]); console.log(o === new Singln2());
上はclass propertyにcacheしておくpatternで、下はconstructor自体を初回に書き換へるpatternだ。下の書き方は副作用が有る。instanceofの結果が変わる。
Singleton normal 1 [ true, true ] { a: 2, b: 3, c: 4 } [ 'Super 2', 'Tool 3 4', '[object Object]' ] true Singleton normal 2 [ true, false ] { a: 2, b: 3, c: 4 } [ 'Super 2', 'Tool 3 4', '[object Object]' ] true
まあ仕方がない。此れと同じ記法を使って、_extends()でSingletonを作る。
console.log('\nSingleton extended 1'); var Single1 = _extends(Super, function (a, b) { if (Single1.instance) { return Single1.instance; } Single1.instance = this; this.c = 4; }); Single1.instance = null; Single1.prototype.tool = function () { return 'Tool ' + this.b + ' ' + this.c; }; o = new Single1(2, 3); console.log([o instanceof Super, o instanceof Single1]); console.log(o); console.log([o.name(), o.tool(), o.toString()]); console.log(o === new Single1());
console.log('\nSingleton extended 2'); var Single2 = _extends(Super, function (a, b) { var me = this; Single2 = function () { return me; } this.c = 4; }); Single2.prototype.tool = function () { return 'Tool ' + this.b + ' ' + this.c; }; o = new Single2(2, 3); console.log([o instanceof Super, o instanceof Single2]); console.log(o); console.log([o.name(), o.tool(), o.toString()]); console.log(o === new Single2());
_extends()を使はない時と全く同じ作り方で済む事が解る。然して此れもうまく動く。
Singleton extended 1 [ true, true ] { a: 2, b: 3, c: 4 } [ 'Super 2', 'Tool 3 4', '[object Object]' ] true Singleton extended 2 [ true, false ] { a: 2, b: 3, c: 4 } [ 'Super 2', 'Tool 3 4', '[object Object]' ] true
結論
使おう(〃l _ l)
// License: Public Domain /** * @param {function(Object...):Object} _super * @param {function(Object...):Object} _constructor * @return {function(Object...):Object} */ function _extends(_super, _constructor) { var _class = null, toString; _class = function() { if (!(this instanceof _class)) { return new (Function.prototype.bind.apply(_class, [ null ].concat(Array.from(arguments)))); } _super.apply(this, arguments); return _constructor.apply(this, arguments); }; _class.prototype = Object.create(_super.prototype); _class.prototype.constructor = _class; toString = Function.prototype.toString; Function.prototype.toString = function () { if (this === _class) { return _constructor.toString(); } return toString.call(this); }; return _class; }
libraryやframworkは其の基底の言語の脳をよくよく理解すべし。