どうも、JavaScriptゆとり世代です。
JavaScriptで継承ってキーワード聞いて、どんなコードを連想しますか?
なんと私が最初に連想したのは、UnderscoreとかBackboneとかのextendでした・・。
※注:うまくまとまる気のしないメモ記事です。
結論からいうと
勘違いというか、そもそも完全に己が無知を晒すことになるんですけど、
いわゆる"継承"で実現したいであろう機能のひとつである、
- 親クラスAがあって、それを継承して子クラスBを作った場合、
- 子クラスBにも、親クラスAにも同名のメソッドFが定義されてても、
- 子クラスBは親クラスAを継承してるので、親のメソッドFを実行することができる
恥ずかしながら、この"子から親の同名のメソッドを実行する"って発想がまるっとなかったんですよね。
むしろそういうことできるから、"継承"って言うんや!っていう。
_.extend()
コードを抜粋。
// Extend a given object with all the properties in passed-in object(s). _.extend = function(obj) { if (!_.isObject(obj)) return obj; _.each(slice.call(arguments, 1), function(source) { for (var prop in source) { obj[prop] = source[prop]; } }); return obj; };
コード見れば一目瞭然なんですけど、いわゆる"継承"ではないんですよね。
ただ単に、プロパティを代入してるだけ。
_.extend(Model.prototype, Events, { // ... };
みたく、prototypeに突っ込めばいわゆる継承っぽいこともできるので、一概に間違ってはいないんやろうけど。
それでもプロパティ名が被ったら、上書きされちゃうし、コピーではないし。
いわゆる継承って何と言われればまた定義が必要になりますが、
なんでこんな風に認識してたのかなーと思うと、単に知らんかったとしか言えず。
単純にプロトタイプをどんどん生やしていくのが"継承"ではないということで。
改めて精進せにゃなーという次第。
JavaScriptゆとり世代な人は要注意?です。
Backbone.**.extend()
せっかくなので、Backboneのもみてみる。
function (protoProps, staticProps) { var parent = this; var child; // The constructor function for the new subclass is either defined by you // (the "constructor" property in your `extend` definition), or defaulted // by us to simply call the parent's constructor. if (protoProps && _.has(protoProps, 'constructor')) { child = protoProps.constructor; } else { child = function(){ return parent.apply(this, arguments); }; } // Add static properties to the constructor function, if supplied. _.extend(child, parent, staticProps); // Set the prototype chain to inherit from `parent`, without calling // `parent`'s constructor function. var Surrogate = function(){ this.constructor = child; }; Surrogate.prototype = parent.prototype; child.prototype = new Surrogate; // Add prototype properties (instance properties) to the subclass, // if supplied. if (protoProps) _.extend(child.prototype, protoProps); // Set a convenience property in case the parent's prototype is needed // later. child.__super__ = parent.prototype; return child; }
というわけで、子で親クラスのメソッドを使いたい場合は、
var Parent = Backbone.Model.extend({ c: function() { console.log('Parent'); } }); var Child = Parent.extend({ c: function() { console.log('Child'); this.constructor.__super__.c(); // => "Parent" } });
ってすればできる。
そもそもなんで
こんなことに今さら気付いたかというと、
CoffeeScriptで書かれたコードを、生JavaScriptのプロジェクトで使いたくって、コンパイルして参考にしようとしたときの話です。
CoffeeScriptの継承イディオムって、コンパイル(そして整形)するとこうなってるんですよね。
var A, B, __hasProp = {}.hasOwnProperty, __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; A = (function() { function A() {} return A; })(); B = (function(_super) { __extends(B, _super); function B() { return B.__super__.constructor.apply(this, arguments); } return B; })(A);
prototypeちゃんと使えるようになってるし、これで十分なのでは?って思ってたけど、
そもそも親に同名の定義があったらどうやって呼ぶのとか、諸々考えるべきことがあるらしく。
そういうわけでJavaScriptは世間を騒がせるんですね・・。
__super__とかconstructorとか、そういうもんってわかってからはだいぶスッキリしてるので、
なんかひとつかしこくなった感すらあります。
うーむ、勉強させていただきます。
@dameleon++