The Dabsong Conshirtoe

技術系の話を主にします。

javascriptでのコード再利用パターン

javascriptでのコード再利用パターンについて最近検討してました。

というか、それまでjsでそれなりに大きなコード書くときも共通部分は関数に分解して、くらいしかしてなかったのですが、プログラムが大きくなるとメンテナンスがきつくなってくるので、普段pythonで書いてるみたいに責務をオブジェクトに分解して特化したい部分は継承する、とかして管理したいなと思いまして。


クラスのような仕組みを構築する

javascriptにはクラスやインスタンスといった概念がないので、シミュレートしてみます。

// 継承をサポートしたいオブジェクトはこのクラスを継承する
var BaseObject = (function() {
  var Base = function() {
    // インスタンス生成時はinitが呼ばれる
    this.init.apply(this, arguments);
    Base.prototype.init = function(){};
  };

  // 継承を行う内部関数
  var inherits = function(parent, props) {
    var child = function() {
      parent.apply(this, arguments);
    };

    var F = function(){};
    F.prototype = parent.prototype;
    child.prototype = new F();

    // オーバーライドするプロパティ
    if (props) {
      for (var key in props) {
        if (props.hasOwnProperty(key)) {
            child.prototype[key] = props[key];
        }
      }
    }
    child.prototype.constructor = child;
    // _super に親への参照を持たせる
    child.prototype._super = parent.prototype;

    return child;
  };

  // 継承関数
  Base.extend = function(protoProps) {
    var child = inherits(this, protoProps);
    child.extend = this.extend;
    return child;
  };

  return Base;
}());

// 親クラス
var Parent = BaseObject.extend({
  init : function(name, age) {
    this.name = name;
    this.age = age;
  },
  say : function() {
    return "I am " + this.name;
  }
});

// 子クラス
var Child = Parent.extend({
  init: function(name, age) {
    this._super.init.call(this, name);
    this.age = age;
  },
  say: function() {
    return this._super.say.call(this) + ", age is " + this.age;
  }
});

// 親クラスのインスタンス生成
var parent = new Parent("parent", 50);
// 子クラスのインスタンス生成
var child = new Child("child", 20);

console.log(parent.say());
"I am parent"
console.log(child.say());
"I am child, age is 20"

長い!!

自分で継承機構を作っているので仕方ないですね。

ParentやChildがクラスで、そのクラスをnewして作っているparentやchildがインスタンスとなります。

extendはクラス継承をシミュレートする関数です。引数に子クラスで定義したいメソッド等をオブジェクトととして渡します。

initはnewしたときに呼ばれる初期化処理です。

インスタンス生成時にnewを忘れると、コンストラクタ内のthisがグローバルオブジェクトを指すので大変なことになります。

ただ、BaseObject生成部分を抜きにして考えれば、クラス定義とインスタンスが明確にわかれているのでそこまで問題にはならないかな。

// BaseObject生成箇所を抜いてみる
// 親クラス
var Parent = BaseObject.extend({
  init : function(name, age) {
    this.name = name;
    this.age = age;
  },
  say : function() {
    return "I am " + this.name;
  }
});

// 子クラス
var Child = Parent.extend({
  init: function(name, age) {
    this._super.init.call(this, name);
    this.age = age;
  },
  say: function() {
    return this._super.say.call(this) + ", age is " + this.age;
  }
});

// 親クラスのインスタンス生成
var parent = new Parent("parent", 50);
// 子クラスのインスタンス生成
var child = new Child("child", 20);

クラスとインスタンスの区別が明確なのは、クラスベースOOPに慣れ親しんでいる人にとっては分かりやすいのではないでしょうか。

継承関数は一度書いてしまえば使いまわせますしね。

newはオライリーのGood Parts本はじめdisられる傾向にありますが、クラスとインスタンスが明確ならnewを忘れることは無さそうですし、忘れたらオブジェクトがundefinedとなるのですぐ気づくような気がします。

みなさんどうしてるのだろう?

ネタ元

この記事はオライリーの「JavaScriptパターン」の6章「コード再利用のパターン」を参考にしています。 また、backbone.jsのコードも参考にしています。

JavaScriptパターン ―優れたアプリケーションのための作法

JavaScriptパターン ―優れたアプリケーションのための作法

JavaScript: The Good Parts ―「良いパーツ」によるベストプラクティス

JavaScript: The Good Parts ―「良いパーツ」によるベストプラクティス