JavaScriptのクラス記述方法

JavaScriptのクラス記述方法のまとめ。まとめてみると意外と知らないことも多くてぼちぼち収穫がありました。

クラス記述

  • ES6のクラス
    • 制約が多い
      • static変数を記述できない
      • private変数を記述できない
  • Object.createの継承
    • 親、子の継承ならば問題は発生しない
    • 親、子、孫の継承で問題が発生する
  • inherits()の継承
    • 採用
  • Babel, TypeScript
    • 時間の余裕があれば、覚えたい

ソース

Sample.js/*!
 * Sample.js
 *
 * Copyright (c) 2018 toshi
 * Released under the MIT license.
 * see https://opensource.org/licenses/MIT
 *
 * The inherits() function is:
 * Copyright (c) Isaac Z. Schlueter
 * Released under the ISC license.
 * see https://opensource.org/licenses/ISC
 */

if (typeof(inherits) === 'undefined') {
  // 継承
  // see https://github.com/isaacs/inherits/blob/master/inherits_browser.js
  function inherits(ctor, superCtor) {
    ctor.super_ = superCtor
    var TempCtor = function () {}
    TempCtor.prototype = superCtor.prototype
    ctor.prototype = new TempCtor()
    ctor.prototype.constructor = ctor
  }
}

(function(global, factory) {
  if (typeof define === 'function' && define.amd) {
    define(factory);
  } else if (typeof exports === 'object') {
    module.exports = factory();
  } else if (!global.SuperClass) {
    global.SuperClass = factory();
  }
})(this, function() {
  "use strict";

  var _this = void 0;
  var _super= Object;
  var _superPrivateVariable = 'super private variable';

  function _superPrivateMethod() {
    console.log('_superPrivateMethod() {');
    console.group();
      console.log('print '+_superPrivateVariable);
    console.groupEnd();
    console.log('}');
  }

  _this = function SuperClass_constructor() {
    console.log('SuperClass_constructor() {');
    console.group();
      SuperClass_initialize.apply(this, arguments);
    console.groupEnd();
    console.log('}');
  }
  inherits(_this, _super);

  _this.superStaticVariable = 'super static variable';

  _this.staticMethod = function SuperClass_staticMethod() {
    console.log('SuperClass_staticMethod() {');
    console.group();
      console.log('print '+_this.superStaticVariable);
    console.groupEnd();
    console.log('}');
  }

  function SuperClass_initialize(arg0, arg1) {
    console.log('SuperClass_initialize('+arg0+', '+arg1+');');

    this.superPublicVariable = 'super public variable';
  }

  _this.prototype.publicMethod = function SuperClass_publicMethod() {
    console.log('SuperClass_publicMethod() {');
    console.group();
      console.log('print '+this.superPublicVariable);
    console.groupEnd();
    console.log('}');
  }

  _this.prototype.superPublicMethod = function SuperClass_superPublicMethod() {
    console.log('SuperClass_superPublicMethod() {');
    console.group();
      console.log('print '+this.superPublicVariable);
    console.groupEnd();
    console.log('}');
  }

  _this.prototype.accessor = function SuperClass_accessor() {
    console.log('SuperClass_accessor() {');
    console.group();
      console.log('call _superPrivateMethod() {');
      console.group();
        _superPrivateMethod();
      console.groupEnd();
      console.log('}');
      console.log('print '+_superPrivateVariable);
      console.log('print '+_this.superStaticVariable);
      console.log('print '+this.superPublicVariable);
    console.groupEnd();
    console.log('}');
  }

  return _this;
});

// UMD (Universal Module Definition)
// see https://github.com/umdjs/umd/blob/master/templates/returnExports.js
(function BaseClass_loader(global, factory) {     // グローバル汚染防止のため、無名関数のほうが良い
  // 実行環境用の処理
  if (typeof define === 'function' && define.amd) {
    // AMD (RequireJSなど)
    console.log('amd');
    define(['SuperClass'], factory);
  } else if (typeof exports === 'object') {
    // Node.js
    console.log('node');
    module.exports = factory(require('SuperClass'));
  } else if (!global.BaseClass) {
    console.log('global');
    global.BaseClass = factory(global.SuperClass);
  }
})(this, function BaseClass_factory(SuperClass) { // グローバル汚染防止のため、無名関数のほうが良い
  "use strict";

  var _this = void 0;
  var _super= SuperClass;

  /**
   * 非公開変数
   */
  var _privateVariable = 'private variable';

  /**
   * 非公開関数
   */
  function _privateMethod() {
    console.log('_privateMethod() {');
    console.group();
      console.log('print '+_privateVariable);
    console.groupEnd();
    console.log('}');
  }

  /**
   * コンストラクタ
   * @constructor
   */
  _this = function BaseClass_constructor() {
    console.log('BaseClass_constructor() {');
    console.group();
      this.initialize.apply(this, arguments);
      //BaseClass__initialize.apply(this, arguments);
    console.groupEnd();
    console.log('}');
  }
  inherits(_this, SuperClass);

  /**
   * 静的変数
   */
  _this.staticVariable = 'static variable';

  /**
   * 定数(大文字、_区切り)
   * 変更できてしまうのが難点
   */
  _this.CONST_VARIABLE = 'CONST_VARIABLE';

  /**
   * 静的関数
   */
  _this.staticMethod = function BaseClass_staticMethod() {
    console.log('BaseClass_staticMethod() {');
    console.group();
      console.log('print '+_this.staticVariable);
    console.groupEnd();
    console.log('}');
  }

  /**
   * 初期化
   */
  _this.prototype.initialize = function BaseClass_initialize(arg0) {  // 公開
  //function BaseClass__initialize(arg0) {                            // 非公開
    console.log('BaseClass_initialize('+arg0+') {');
    console.group();

      console.log('call _super.initialize() {');
      console.group();
        _super.call(this, arg0, 'base arg1');   // 引数を指定して渡す
        //_super.apply(this, arguments);        // 引数をそのまま渡す
      console.groupEnd();
      console.log('}');

      /**
       * メンバ変数
       */
      this.publicVariable = 'public variable';

      /**
       * プロテクト変数(_接頭詞)
       * 変更できてしまうのが難点
       */
      this._protectedVariable = 'protected variable';
    console.groupEnd();
    console.log('}');
  }

  /**
   * メンバ関数
   */
  _this.prototype.publicMethod = function BaseClass_publicMethod() {
    console.log('BaseClass_publicMethod() {');
    console.group();
      console.log('print '+this.publicVariable);
      console.log('call _super.publicMethod() {');
      console.group();
        _super.prototype.publicMethod.call(this);
      console.groupEnd();
      console.log('}');
      console.log('print '+this.superPublicVariable);
    console.groupEnd();
    console.log('}');
  }

  _this.prototype.accessor = function BaseClass_accessor() {
    console.log('BaseClass_accessor() {');
    console.group();
      console.log('call _privateMethod() {');
      console.group();
        _privateMethod();
      console.groupEnd();
      console.log('}');
      console.log('call _super.accessor.call(this) {');
      console.group();
        _super.prototype.accessor.call(this);
      console.groupEnd();
      console.log('}');
      console.log('print '+_privateVariable);
      console.log('print '+_this.staticVariable);
      console.log('print '+this.publicVariable);
      console.log('print '+_super.superStaticVariable);
      console.log('print '+this.superPublicVariable);
    console.groupEnd();
    console.log('}');
  }

  // BaseClassのコンストラクタ
  return _this;
});

呼び出し例

mainfunction main() {
  console.log('main() {');
  console.group();

    console.log('');
    console.log('静的関数');
    BaseClass.staticMethod();

    console.log('');
    console.log('インスタンス作成');
    var sample = new BaseClass('arg0');

    console.log('');
    console.log('メンバ関数');
    sample.publicMethod();

    console.log('');
    console.log('メンバ関数(親クラス)');
    sample.superPublicMethod();

    console.log('');
    console.log('アクセサ');
    sample.accessor();

  console.groupEnd();
  console.log('}');
}

実行結果例

結果global
main() {

  静的関数
  BaseClass_staticMethod() {
    print static variable
  }

  インスタンス作成
  BaseClass_constructor() {
    BaseClass_initialize(arg0) {
      call _super.initialize() {
        SuperClass_constructor() {
          SuperClass_initialize(arg0, base arg1);
        }
      }
    }
  }

  メンバ関数
  BaseClass_publicMethod() {
    print public variable
    call _super.publicMethod() {
      SuperClass_publicMethod() {
        print super public variable
      }
    }
    print super public variable
  }

  メンバ関数(親クラス)
  SuperClass_superPublicMethod() {
    print super public variable
  }

  アクセサ
  BaseClass_accessor() {
    call _privateMethod() {
      _privateMethod() {
        print private variable
      }
    }
    call _super.accessor.call(this) {
      SuperClass_accessor() {
        call _superPrivateMethod() {
          _superPrivateMethod() {
            print super private variable
          }
        }
        print super private variable
        print super static variable
        print super public variable
      }
    }
    print private variable
    print static variable
    print public variable
    print super static variable
    print super public variable
  }
}

補足

便宜上MITライセンスを指定していますが、上記のクラスをそのまま使用することは決してないと思います。変数、関数へのアクセス関連の考え方は、「このコードは標準的な技術を持つ技術者であれば、別個に思いついた可能性がある」 と考えます。そのため、著作権表示等なく使用していただいて構いません。(Sample.jsとしては、MITライセンスの対象となります)

また、inherits関数は、筆者のコードではありません。記載の著作権表示、ライセンス表示の通りです。

参考