c4se記:さっちゃんですよ☆

.。oO(さっちゃんですよヾ(〃l _ l)ノ゙☆)

.。oO(此のblogは、主に音樂考察Programming に分類されますよ。ヾ(〃l _ l)ノ゙♬♪♡)

音樂は SoundCloud に公開中です。

考察は現在は主に Scrapbox で公表中です。

Programming は GitHub で開發中です。

JavaScriptで「普通に」継承する

軈て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は其の基底の言語の脳をよくよく理解すべし。