JavaScriptのPrivateプロパティを試作してみた

# はじめに

古いJavaScriptではオブジェクトのプライベート変数(プロパティ)を持つのが難しいのでクロージャーを使って実装します。書き方をよく忘れてしまうのでメモしておきます。

ESなんとかの新しいJSだともっと書きやすいとは思いますが、元々のJavaScriptではどうなっているのか、というところの勉強としての記事なのでESなんとかは対象外です。

# コード説明
下記コードでは、newを使う使わない、どちらでもオブジェクトを生成できるPoint型を定義しています。

* main01 は通常のオブジェクトの生成方法。これだと、プライベートプロパティを持つことができません。

* main02 はクロージャー使って、_xと_yをオブジェクトに固定して持っているので、プライベートプロパティを持つことができます。アクセスメソッドをオブジェクトに定義しています。
このアクセスメソッドは、prototypeでは宣言できません。

* main03 は、試しにアクセスメソッドをprototypeにて定義してみました。誤動作するのがわかります。


```js:index.js

"use strict";

var main01 = function () {

  var Point = function(x, y) {
    if (!(this instanceof Point)) {
      return new Point(x, y);
    }
    this._x = x;
    this._y = y;
  };

  Point.prototype.x = function() {
    return this._x;
  };

  Point.prototype.y = function() {
    return this._y;
  };

  Point.prototype.distance = function(point) {
    return Math.sqrt(
      Math.pow(point.x() - this._x, 2) +
      Math.pow(point.y() - this._y, 2) );
  };

  var p1 = Point(0,0);
  var p2 = Point(1,1);
  alert(p1.distance(p2).toString());  //1.4142135623730951
  alert(p2._x);                       //1

  var p3 = Point(0,0);
  var p4 = Point(1,1);
  alert(p3.distance(p4).toString());  //1.4142135623730951
  alert(p4._x);                       //1
};

var main02 = function () {

  var Point = function(_x, _y) {
    if (!(this instanceof Point)) {
      return new Point(_x, _y);
    }

    this.x = function() {
      return _x;
    };

    this.y = function() {
      return _y;
    };

    Point.prototype.distance = function(point) {
      return Math.sqrt(
        Math.pow(point.x() - this.x(), 2) +
        Math.pow(point.y() - this.y(), 2) );
    };
  };

  var p1 = new Point(0,0);
  var p2 = new Point(1,1);
  alert(p1.distance(p2).toString());  //1.4142135623730951
  alert(p2._x);                       //undefined

  var p3 = Point(0,0);
  var p4 = Point(1,1);
  alert(p3.distance(p4).toString());  //1.4142135623730951
  alert(p4._x);                       //undefined
};


var main03 = function () {

  var Point = function(_x, _y) {
    if (!(this instanceof Point)) {
      return new Point(_x, _y);
    }

    Point.prototype.x = function() {
      return _x;
    };

    Point.prototype.y = function() {
      return _y;
    };

    Point.prototype.distance = function(point) {
      return Math.sqrt(
        Math.pow(point.x() - this.x(), 2) +
        Math.pow(point.y() - this.y(), 2) );
    };
  };

  var p1 = Point(0,0);
  var p2 = Point(1,1);
  alert(p1.distance(p2).toString());  //0
  alert(p2._x);                       //undefined

  var p3 = Point(0,0);
  var p4 = Point(1,1);
  alert(p3.distance(p4).toString());  //0
  alert(p4._x);                       //undefined
};

var main = function() {
  main01();
  main02();
  main03();
};

~~~

動作確認は下記のindex.htmlで行いました。

WSH JScriptでも動くように index.wsf も作りました。

~~~index.html
<!DOCTYPE html>
<html lang="ja"><head>
  <meta charset="utf-8">
  <script src="./index.js"></script>

<script>

document.addEventListener("DOMContentLoaded",function(eve){
    main();
},false);

</script>

</head><body>
</body></html>
~~~


~~~xml:index.wsf
<?xml version="1.0" encoding="shift-jis" ?>
<job>
    <script language="JavaScript" src=".\index.js"></script>
    <script language="JavaScript">
    <![CDATA[

var alert = function (message) {
  if (typeof message === 'undefined') {
    WScript.Echo('undefined');
  } else {
    WScript.Echo(message);
  }
};

main();

    ]]>
    </script>
</job>

~~~

# 結果

プライベートプロパティはアクセスメソッドがprototypeではなく作らなければならないので、やや不便です。
オブジェクトごとにメソッドを定義するのは、何十何百とオブジェクトを生成するときに無駄が多いです。
JSは負荷(何百というオブジェクト生成などなど？よくわからないけれども)をかけると原因不明でブラウザ上で止まるときがあり、そのようなことはなるべく避けたいので、この書き方(main02のやり方)は推奨しません。

単純にアンダーバーを付属したプロパティとして作り、利用者側に「使わないでください」と示す程度の方(main01のやり方)がいいと感じます。