[解決済み] JavaScriptでカスタムオブジェクトを「正しく」作成する方法とは?
質問
プロパティとメソッドを持つJavaScriptオブジェクトを作るにはどうしたらいいのでしょうか?
を使っている例を見たことがあります。
var self = this
を使用し、その後
self.
をすべての関数で使用し、スコープが常に正しいことを確認します。
では
.prototype
を使用してプロパティを追加する人もいれば、インラインで追加する人もいます。
誰か、いくつかのプロパティとメソッドを持つJavaScriptオブジェクトの適切な例を教えてください。
どのように解決するのですか?
JavaScriptでクラスとインスタンスを実装する方法には、プロトタイピング方式とクロージャ方式の2つのモデルがあります。どちらにも利点と欠点があり、たくさんの拡張バリエーションがあります。多くのプログラマやライブラリが異なるアプローチやクラス処理のユーティリティ関数を持っており、言語の醜い部分のいくつかをカバーしてくれています。
その結果、混在する会社ではメタクラスが寄せ集めとなり、それぞれが微妙に異なる挙動をすることになります。さらに悪いことに、ほとんどのJavaScriptのチュートリアル教材はひどいもので、すべてのベースをカバーするためにある種の中間的な妥協点を提供し、あなたを非常に混乱させたままにしておきます。(おそらく作者もそうでしょう。JavaScriptのオブジェクトモデルは、ほとんどのプログラミング言語と非常に異なっており、多くの場所でストレートにひどく設計されています)。
まずは プロトタイプの方法 . これは最もJavaScriptネイティブな方法です。オーバーヘッドのコードは最小限に抑えられ、instanceofはこの種のオブジェクトのインスタンスに対して動作します。
function Shape(x, y) {
this.x= x;
this.y= y;
}
で生成されたインスタンスにメソッドを追加することができる。
new Shape
に書き込むことで
prototype
このコンストラクタ関数のルックアップになります。
Shape.prototype.toString= function() {
return 'Shape at '+this.x+', '+this.y;
};
さて、JavaScriptが行うことをサブクラスと呼ぶことができる範囲で、これをサブクラス化します。そのためには、あの奇妙な魔法を完全に置き換えてしまいます。
prototype
プロパティを使用します。
function Circle(x, y, r) {
Shape.call(this, x, y); // invoke the base class's constructor function to take co-ords
this.r= r;
}
Circle.prototype= new Shape();
にメソッドを追加する前に
Circle.prototype.toString= function() {
return 'Circular '+Shape.prototype.toString.call(this)+' with radius '+this.r;
}
この例は動作しますし、多くのチュートリアルでこのようなコードを見ることができます。しかし、この
new Shape()
実際のShapeが作成されないにもかかわらず、基底クラスをインスタンス化しているのです。このような単純なケースでうまくいくのは、JavaScriptが非常に杜撰で、引数をゼロで渡すことができ、その場合
x
と
y
になる
undefined
に割り当てられ、プロトタイプの
this.x
と
this.y
. もし、コンストラクタ関数がもっと複雑なことをするのであれば、このままでは破綻してしまうでしょう。
そこで、ベースクラスのコンストラクタ関数を呼び出すことなく、クラスレベルで必要なメソッドやその他のメンバを含むプロトタイプオブジェクトを作成する方法を見つけることが必要です。そのためには、ヘルパーコードを書き始めなければなりません。これが、私が知っている最もシンプルな方法です。
function subclassOf(base) {
_subclassOf.prototype= base.prototype;
return new _subclassOf();
}
function _subclassOf() {};
これは、ベースクラスのプロトタイプのメンバを、何もしない新しいコンストラクタ関数に転送し、そのコンストラクタを使用します。これで簡単に書けるようになった。
function Circle(x, y, r) {
Shape.call(this, x, y);
this.r= r;
}
Circle.prototype= subclassOf(Shape);
の代わりに
new Shape()
の誤りです。これで、クラスを構築するためのプリミティブの許容範囲内が揃いました。
このモデルのもとでは、いくつかの改良と拡張を検討することができます。例えば、以下は構文的な砂糖のバージョンです。
Function.prototype.subclass= function(base) {
var c= Function.prototype.subclass.nonconstructor;
c.prototype= base.prototype;
this.prototype= new c();
};
Function.prototype.subclass.nonconstructor= function() {};
...
function Circle(x, y, r) {
Shape.call(this, x, y);
this.r= r;
}
Circle.subclass(Shape);
どちらのバージョンでも、多くの言語でそうであるように、コンストラクタ関数を継承できないという欠点があります。そのため、たとえサブクラスが構築プロセスに何も追加しなくても、ベースが望んだ引数でベースのコンストラクタを呼び出すことを覚えておかなければなりません。これを少し自動化するには
apply
しかし、それでも書き出さなければならない。
function Point() {
Shape.apply(this, arguments);
}
Point.subclass(Shape);
そこで、一般的な拡張方法として、初期化処理をコンストラクタ自体ではなく、独自の関数に分割する方法があります。この関数は、ベースからうまく継承することができます。
function Shape() { this._init.apply(this, arguments); }
Shape.prototype._init= function(x, y) {
this.x= x;
this.y= y;
};
function Point() { this._init.apply(this, arguments); }
Point.subclass(Shape);
// no need to write new initialiser for Point!
これで、各クラスに同じコンストラクタ関数の定型文ができただけです。これを独自のヘルパー関数に移動して、何度もタイプする必要がないようにしましょう。
Function.prototype.subclass
というように、ベースクラスのFunctionからサブクラスが吐き出されるようになっています。
Function.prototype.makeSubclass= function() {
function Class() {
if ('_init' in this)
this._init.apply(this, arguments);
}
Function.prototype.makeSubclass.nonconstructor.prototype= this.prototype;
Class.prototype= new Function.prototype.makeSubclass.nonconstructor();
return Class;
};
Function.prototype.makeSubclass.nonconstructor= function() {};
...
Shape= Object.makeSubclass();
Shape.prototype._init= function(x, y) {
this.x= x;
this.y= y;
};
Point= Shape.makeSubclass();
Circle= Shape.makeSubclass();
Circle.prototype._init= function(x, y, r) {
Shape.prototype._init.call(this, x, y);
this.r= r;
};
...少し不器用な構文ではありますが、他の言語に似てきていますね。好きなように追加機能を散りばめることができる。例えば
makeSubclass
は、クラス名を取得して記憶し、デフォルトの
toString
を使用しています。コンストラクタが誤って
new
演算子を使用します (そうしないと、非常に面倒なデバッグになることがよくあります)。
Function.prototype.makeSubclass= function() {
function Class() {
if (!(this instanceof Class))
throw('Constructor called without "new"');
...
もしかしたら、新しいメンバー全員を合格させて
makeSubclass
を書く手間を省くために、プロトタイプにそれらを追加します。
Class.prototype...
というように、かなり多くなっています。多くのクラスシステムがそうなっています。
Circle= Shape.makeSubclass({
_init: function(x, y, z) {
Shape.prototype._init.call(this, x, y);
this.r= r;
},
...
});
オブジェクト・システムに望ましいと思われる機能はたくさんあり、ある特定の方式に同意する人はいません。
その クロージャーの方法 ということです。これは、JavaScriptのプロトタイプベースの継承の問題点を、継承を全く使わないことで回避しています。その代わりに
function Shape(x, y) {
var that= this;
this.x= x;
this.y= y;
this.toString= function() {
return 'Shape at '+that.x+', '+that.y;
};
}
function Circle(x, y, r) {
var that= this;
Shape.call(this, x, y);
this.r= r;
var _baseToString= this.toString;
this.toString= function() {
return 'Circular '+_baseToString(that)+' with radius '+that.r;
};
};
var mycircle= new Circle();
これで、すべての
Shape
は、そのコピーに
toString
メソッド (およびその他のメソッドや追加するクラスメンバ) を追加します。
すべてのインスタンスが各クラスメンバーのコピーを持つことの悪い点は、効率が悪いことです。もし、大量のサブクラス化されたインスタンスを扱うのであれば、プロトタイプ継承の方がよいかもしれません。また、ベースクラスのメソッドを呼び出すのは、ご覧の通り少し面倒です。サブクラスのコンストラクタが上書きする前のメソッドを覚えておかなければなりませんし、そうでなければ失われてしまいます。
[また、ここでは継承を行わないので
instanceof
演算子は動作しません。必要であれば、クラス・スニッフィングのための独自のメカニズムを提供しなければなりません。一方
できる
プロトタイプ継承と同じようにプロトタイプオブジェクトをいじくり回すのは、ちょっとやっかいですし
instanceof
が動作するようになりました] 。
すべてのインスタンスが独自のメソッドを持つことの良い点は、そのメソッドを所有する特定のインスタンスにバインドすることができることです。これは、JavaScriptの奇妙なバインディングの方法によって、便利です。
this
メソッド呼び出しの中で、メソッドをそのオーナーから切り離すと、このような結果になります。
var ts= mycircle.toString;
alert(ts());
では
this
メソッドの内部は、予想通りCircleインスタンスにはならない(実際には、グローバルな
window
オブジェクトが作成され、デバッグに大きな影響を与えることになります。) 実際には、このような現象は、メソッドを取り出して
setTimeout
,
onclick
または
EventListener
を一般的に使用します。
プロトタイプの方法では、そのような割り当てのためにクロージャを含めなければなりません。
setTimeout(function() {
mycircle.move(1, 1);
}, 1000);
あるいは、将来的には(あるいは今Function.prototypeをハックすれば)、以下のようにすることもできます。
function.bind()
:
setTimeout(mycircle.move.bind(mycircle, 1, 1), 1000);
インスタンスがクロージャ方式で作られている場合、バインディングはクロージャによってインスタンス変数(通常は
that
または
self
のように、個人的には後者をお勧めします。
self
は、JavaScriptではすでに別の別の意味を持っています)。引数
1, 1
を使用すると、別のクロージャまたは
bind()
を行う必要がある場合。
クロージャーメソッドにもたくさんのバリエーションがあります。を省略することができます。
this
を完全に作成し、新しい
that
を使用する代わりに、それを返します。
new
演算子を使用します。
function Shape(x, y) {
var that= {};
that.x= x;
that.y= y;
that.toString= function() {
return 'Shape at '+that.x+', '+that.y;
};
return that;
}
function Circle(x, y, r) {
var that= Shape(x, y);
that.r= r;
var _baseToString= that.toString;
that.toString= function() {
return 'Circular '+_baseToString(that)+' with radius '+r;
};
return that;
};
var mycircle= Circle(); // you can include `new` if you want but it won't do anything
どちらが「正しい」のでしょうか?両方です。どちらが「最善」なのか?それはあなたの状況次第です。ちなみに私は、OOを強く意識しているときは、本当のJavaScriptの継承はプロトタイピングに、単純な使い捨てのページ効果はクロージャにする傾向があります。
しかし、どちらの方法も、ほとんどのプログラマーにとっては、かなり直感に反するものです。どちらも多くの厄介なバリエーションがある可能性があります。他の人のコードやライブラリを使えば、その両方(と同時に多くの中間的で一般的に壊れたスキーム)に出会うことになります。一般的に受け入れられている答えはひとつもありません。JavaScript のオブジェクトの素晴らしい世界へようこそ。
[これは「なぜJavaScriptは私の好きなプログラミング言語ではないのか」の第94回である] 。
関連
-
Vueがechartsのtooltipにクリックイベントを追加するケーススタディ
-
[解決済み】React-Redux: アクションはプレーンオブジェクトでなければならない。非同期アクションにはカスタムミドルウェアを使用する
-
[解決済み] 配列から特定の項目を削除するにはどうすればよいですか?
-
[解決済み] JavaScriptで "use strict "は何をするのか、その根拠は?
-
[解決済み] JavaScriptで文字列が部分文字列を含むかどうかを確認する方法は?
-
[解決済み] あるJavaScriptファイルを他のJavaScriptファイルにインクルードするにはどうすればよいですか?
-
[解決済み] JavaScriptでオブジェクトをディープクローンする最も効率的な方法は何ですか?
-
[解決済み] GUID / UUIDの作成方法
-
[解決済み] JavaScriptのオブジェクトが空であることをテストするにはどうすればよいですか?
-
[解決済み】オブジェクトからプロパティを削除する(JavaScript)
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
vue3.0プロジェクトのアーキテクチャを構築するための便利なツール
-
jQueryのコピーオブジェクトの説明
-
[解決済み] Error : 未定義のプロパティ 'map' を読み取ることができません。
-
[解決済み】"フォームが接続されていないため、フォームの送信がキャンセルされました "というエラーの取得について
-
[解決済み】「X-Frame-Options」を「SAMEORIGIN」に設定したため、フレームでの表示を拒否された。
-
[解決済み】TypeErrorの解決方法。未定義またはヌルをオブジェクトに変換できない
-
[解決済み】TypeScript-のAngular Frameworkエラー - "exportAsがngFormに設定されたディレクティブはありません"
-
[解決済み】JavaScriptでインラインIF文の書き方は?
-
[解決済み】React-Redux: アクションはプレーンオブジェクトでなければならない。非同期アクションにはカスタムミドルウェアを使用する
-
[解決済み] JavaScriptプログラマーが知っておくべきこととは?[クローズド]