はじめに
古いJavaScriptではオブジェクトのプライベート変数(プロパティ)を持つのが難しいのでクロージャーを使って実装します。書き方をよく忘れてしまうのでメモしておきます。
ESなんとかの新しいJSだともっと書きやすいとは思いますが、元々のJavaScriptではどうなっているのか、というところの勉強としての記事なのでESなんとかは対象外です。
コード説明
下記コードでは、newを使う使わない、どちらでもオブジェクトを生成できるPoint型を定義しています。
-
main01 は通常のオブジェクトの生成方法。これだと、プライベートプロパティを持つことができません。
-
main02 はクロージャー使って、_xと_yをオブジェクトに固定して持っているので、プライベートプロパティを持つことができます。アクセスメソッドをオブジェクトに定義しています。
このアクセスメソッドは、prototypeでは宣言できません。 -
main03 は、試しにアクセスメソッドをprototypeにて定義してみました。誤動作するのがわかります。
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のやり方)がいいと感じます。