カイルシンプソンの「OLOO(他のオブジェクトにリンクするオブジェクト)パターン」は、プロトタイプデザインパターンとどのように異なりますか? 「リンク」(プロトタイプの動作)を具体的に示すものでそれを作り出し、ここで発生する「コピー」(クラスの動作)がないことを明確にする以外に、彼のパターンは正確に何を導入しますか?
カイルのパターンの例 彼の本「JSを知らない:this&Object Prototypes」から:
var Foo = {
init: function(who) {
this.me = who;
},
identify: function() {
return "I am " + this.me;
}
};
var Bar = Object.create(Foo);
Bar.speak = function() {
alert("Hello, " + this.identify() + ".");
};
var b1 = Object.create(Bar);
b1.init("b1");
var b2 = Object.create(Bar);
b2.init("b2");
b1.speak(); // alerts: "Hello, I am b1."
b2.speak(); // alerts: "Hello, I am b2."
彼のパターンは正確に何を紹介していますか?
OLOOは、リンクを取得するために他の(IMOを混乱させる)セマンティクスに重ねる必要なく、プロトタイプチェーンをそのまま採用します。
したがって、これらの2つのスニペットはまったく同じ結果になりますが、異なる方法で到達します。
コンストラクター形式:
function Foo() {}
Foo.prototype.y = 11;
function Bar() {}
Bar.prototype = Object.create(Foo.prototype);
Bar.prototype.z = 31;
var x = new Bar();
x.y + x.z; // 42
OLOOフォーム:
var FooObj = { y: 11 };
var BarObj = Object.create(FooObj);
BarObj.z = 31;
var x = Object.create(BarObj);
x.y + x.z; // 42
両方のスニペットで、x
オブジェクトは[[Prototype]]
-にリンクされ、オブジェクト(Bar.prototype
またはBarObj
)にリンクされ、3番目のオブジェクト(Foo.prototype
またはFooObj
)。
スニペット間の関係と委任は同一です。メモリ使用量はスニペット間で同一です。多くの「子」(別名、x1
からx1000
などの多くのオブジェクト)を作成する機能は、スニペット間で同じです。委任のパフォーマンス(x.y
およびx.z
)は、スニペット間で同一です。オブジェクト作成のパフォーマンスis OLOOでは遅くなりますが、 健全性チェック は、パフォーマンスの低下が実際に問題ではないことを示しています。
OLOOが提供するものは、コンストラクタ/ new
メカニズムを介して間接的にリンクするよりも、オブジェクトを単に表現して直接リンクする方がはるかに簡単だということです。後者はクラスに関するふりをしますが、実際には委任を表現するためのひどい構文です(サイドノート:ES6 class
構文も!) 。
OLOOは中間者を切り取っているだけです。
別の比較 of class
vs OLOOです。
私はカイルの本を読みましたが、特にthis
がどのようにバインドされているかについての詳細は、非常に有益であることがわかりました。
私にとっては、OLOOの大きな利点がいくつかあります。
OLOOはObject.create()
に依存して、別のオブジェクトに[[prototype]]
リンクされた新しいオブジェクトを作成します。関数がprototype
プロパティを持っていることを理解したり、その変更に起因する潜在的な関連する落とし穴を心配する必要はありません。
これは議論の余地がありますが、OLOO構文は(多くの場合)より洗練され、「標準」のjavascriptアプローチよりも簡潔で、特にポリモーフィズム(super
- style呼び出し)に関してはそう感じています。
疑わしい設計が1つあると思います(実際に上記の2に貢献するもの)。それはシャドーイングに関するものです。
振る舞いの委任では、
[[Prototype]]
チェーンの異なるレベルで考えられるすべてのものに同じ名前を付けることを避けます。
この背後にある考え方は、オブジェクトが独自のより具体的な関数を持ち、それが内部的にチェーンの下位の関数に委任されるというものです。たとえば、オブジェクトのJSONバージョンをサーバーに送信するsave()
関数を持つresource
オブジェクトがありますが、stripAndSave()
関数を持つclientResource
オブジェクトもあります。最初に、サーバーに送信すべきではないプロパティを削除します。
潜在的な問題は、誰かがやって来て、プロトタイプチェーン全体を完全に認識せずにspecialResource
オブジェクトを作成することに決めた場合、ベースをシャドウするsave
というプロパティの下に最後の保存のタイムスタンプを保存することを合理的に決定するかもしれませんresource
オブジェクトのsave()
機能は、プロトタイプチェーンの2つのリンクにあります。
var resource = {
save: function () {
console.log('Saving');
}
};
var clientResource = Object.create(resource);
clientResource.stripAndSave = function () {
// Do something else, then delegate
console.log('Stripping unwanted properties');
this.save();
};
var specialResource = Object.create( clientResource );
specialResource.timeStampedSave = function () {
// Set the timestamp of the last save
this.save = Date.now();
this.stripAndSave();
};
a = Object.create(clientResource);
b = Object.create(specialResource);
a.stripAndSave(); // "Stripping unwanted properties" & "Saving".
b.timeStampedSave(); // Error!
これは特に不自然な例ですが、ポイントは具体的にはnot他のプロパティをシャドウイングすると、いくつかの厄介な状況とシソーラスの大量使用につながる可能性があるということです!
おそらく、これのより良い例はinit
メソッドでしょう。特にOOLOサイドステップコンストラクター型関数としては痛烈です。関連するすべてのオブジェクトにこのような関数が必要になる可能性が高いため、それらに適切な名前を付けるのは退屈な作業になる可能性があります。
*実際には特に合理的ではありません(lastSaved
の方がはるかに優れていますが、これは単なる例です)。
「JSを知らない:this&Object Prototypes」での議論とOLOOのプレゼンテーションは考えさせるものであり、私はこの本を通してたくさんのことを学びました。 OLOOパターンのメリットは、他の回答で詳しく説明されています。ただし、以下のペットに関する苦情があります(または、効果的に適用できないようなものがありません)。
1
「クラス」が古典的なパターンで別の「クラス」を「継承」する場合、2つの関数は同様の構文( 「関数宣言」または「関数ステートメント」 )として宣言できます。
function Point(x,y) {
this.x = x;
this.y = y;
};
function Point3D(x,y,z) {
Point.call(this, x,y);
this.z = z;
};
Point3D.prototype = Object.create(Point.prototype);
対照的に、OLOOパターンでは、ベースおよび派生オブジェクトを定義するために使用されるさまざまな構文形式:
var Point = {
init : function(x,y) {
this.x = x;
this.y = y;
}
};
var Point3D = Object.create(Point);
Point3D.init = function(x,y,z) {
Point.init.call(this, x, y);
this.z = z;
};
上記の例でわかるように、ベースオブジェクトはオブジェクトリテラル表記を使用して定義できますが、派生オブジェクトに同じ表記を使用することはできません。この非対称性は私を悩ませます。
2
OLOOパターンでは、オブジェクトの作成は2つのステップです。
Object.create
を呼び出しますオブジェクトを初期化するために、いくつかのカスタムの非標準メソッドを呼び出します(オブジェクトごとに異なる場合があるため、覚えておく必要があります)。
var p2a = Object.create(Point);
p2a.init(1,1);
対照的に、Prototypeパターンでは、標準演算子new
を使用します。
var p2a = new Point(1,1);
3
古典的なパターンでは、(.prototype
とは対照的に) "クラス"関数に直接割り当てることにより、 "インスタンス"に直接適用しない "静的な"ユーティリティ関数を作成できます。例えば。以下のコードの関数square
のように:
Point.square = function(x) {return x*x;};
Point.prototype.length = function() {
return Math.sqrt(Point.square(this.x)+Point.square(this.y));
};
対照的に、OLOOパターンでは、オブジェクトインスタンスでも([[プロトタイプ]]チェーンを介して)すべての「静的」関数を使用できます。
var Point = {
init : function(x,y) {
this.x = x;
this.y = y;
},
square: function(x) {return x*x;},
length: function() {return Math.sqrt(Point.square(this.x)+Point.square(this.y));}
};
「各オブジェクトを他のオブジェクトに依存させるように考えました」
Kyleが2つのオブジェクトが[[Prototype]]
リンクされていると説明するように、それらは互いに依存していません。代わりに、それらは個別のオブジェクトです。 [[Prototype]]
リンケージを使用して、あるオブジェクトを別のオブジェクトにリンクしているので、いつでも変更できます。 OLOOスタイルで作成された2つの[[Prototype]]
リンクオブジェクトを互いに依存している場合、constructor
呼び出しで作成されたオブジェクトについても同様に考える必要があります。
var foo= {},
bar= Object.create(foo),
baz= Object.create(bar);
console.log(Object.getPrototypeOf(foo)) //Object.prototype
console.log(Object.getPrototypeOf(bar)) //foo
console.log(Object.getPrototypeOf(baz)) //bar
では、foo
bar
とbaz
がお互いに依存していると思いますか?
では、これと同じconstructor
スタイルのコードをやってみましょう。
function Foo() {}
function Bar() {}
function Baz() {}
Bar.prototype= Object.create(Foo);
Baz.prototype= Object.create(Bar);
var foo= new Foo(),
bar= new Bar().
baz= new Baz();
console.log(Object.getPrototypeOf(foo)) //Foo.prototype
console.log(Object.getPrototypeOf(Foo.prototype)) //Object.prototype
console.log(Object.getPrototypeOf(bar)) //Bar.prototype
console.log(Object.getPrototypeOf(Bar.prototype)) //Foo.prototype
console.log(Object.getPrototypeOf(baz)) //Baz.prototype
console.log(Object.getPrototypeOf(Baz.prototype)) //Bar.prototype
後者と前者のコードの唯一の違いは、後者のfoo
、bar
、baz
bbjectsは、constructor
関数の任意のオブジェクトを介して互いにリンクされていることです(Foo.prototype
、Bar.prototype
、 Baz.prototype
)しかし、前者(OLOO
スタイル)では直接リンクされます。両方の方法で、単にfoo
、bar
、baz
を前者では直接、後者では間接的にリンクしています。ただし、どちらの場合も、オブジェクトは相互に独立しています。なぜなら、インスタンス化されたクラスは、他のクラスから継承させることができないクラスのインスタンスのようではないからです。オブジェクトが委任するオブジェクトもいつでも変更できます。
var anotherObj= {};
Object.setPrototypeOf(foo, anotherObj);
したがって、それらはすべて互いに独立しています。
「
OLOO
が、各オブジェクトが他のオブジェクトについて何も知らないという問題を解決することを望んでいました。」
はい、それは確かに可能です-
Tech
をユーティリティオブジェクトとして使用しましょう。
var Tech= {
tag: "technology",
setName= function(name) {
this.name= name;
}
}
Tech
-にリンクするオブジェクトをいくつでも作成します
var html= Object.create(Tech),
css= Object.create(Tech),
js= Object.create(Tech);
Some checking (avoiding console.log)-
html.isPrototypeOf(css); //false
html.isPrototypeOf(js); //false
css.isPrototypeOf(html); //false
css.isPrototypeOf(js); //false
js.isPrototypeOf(html); //false
js.isPrototypwOf(css); //false
Tech.isPrototypeOf(html); //true
Tech.isPrototypeOf(css); //true
Tech.isPrototypeOf(js); //true
html
、css
、js
オブジェクトは互いに接続されていると思いますか?いいえ、そうではありません。それでは、constructor
関数を使用してどのように処理できたかを見てみましょう。
function Tech() { }
Tech.prototype.tag= "technology";
Tech.prototype.setName= function(name) {
this.name= name;
}
Tech.proptotype
-にリンクするオブジェクトをいくつでも作成します
var html= new Tech(),
css= new Tech(),
js= new Tech();
いくつかのチェック(console.logの回避)-
html.isPrototypeOf(css); //false
html.isPrototypeOf(js); //false
css.isPrototypeOf(html); //false
css.isPrototypeOf(js); //false
js.isPrototypeOf(html); //false
js.isPrototypeOf(css); //false
Tech.prototype.isPrototypeOf(html); //true
Tech.prototype.isPrototypeOf(css); //true
Tech.prototype.isPrototypeOf(js); //true
これらのconstructor
スタイルのオブジェクト(html
、css
、js
)オブジェクトはOLOO
スタイルのコードとどう違うと思いますか?実際、それらは同じ目的を果たします。 OLOO
スタイルの1つのオブジェクトはTech
に委任します(委任は明示的に設定されました)が、constructor
スタイルの1つのオブジェクトはTech.prototype
に委任します(委任は暗黙的に設定されました)。最終的には、OLOO
- styleを直接使用し、constructor
- styleを間接的に使用して、互いにリンクされていない3つのオブジェクトを1つのオブジェクトにリンクします。
「ObjBは、ObjAから作成する必要があります。Object.create(ObjB)など」
いいえ、ここのObjB
は、クラスObjA
のインスタンス(従来の言語では)とは異なります。 作成時にobjB
オブジェクトをObjA
オブジェクトにデリゲートするように言う必要があります。コンストラクタを使用した場合、間接的に.prototype
s。
@Marcus @bholben
おそらく私たちはこのようなことをすることができます。
const Point = {
statics(m) { if (this !== Point) { throw Error(m); }},
create (x, y) {
this.statics();
var P = Object.create(Point);
P.init(x, y);
return P;
},
init(x=0, y=0) {
this.x = x;
this.y = y;
}
};
const Point3D = {
__proto__: Point,
statics(m) { if (this !== Point3D) { throw Error(m); }},
create (x, y, z) {
this.statics();
var P = Object.create(Point3D);
P.init(x, y, z);
return P;
},
init (x=0, y=0, z=0) {
super.init(x, y);
this.z = z;
}
};
もちろん、Point2DオブジェクトのプロトタイプにリンクするPoint3Dオブジェクトを作成するのはちょっと馬鹿げていますが、それはポイントの横にあります(私はあなたの例と一致したかったです)。とにかく、苦情に関する限り:
非対称性は、ES6の Object.setPrototypeOf で修正するか、使用している__proto__ = ...
にもっと眉をひそめます。 Point3D.init()
に見られるように、通常のオブジェクトでも super を使用することもできます。別の方法は、次のようなことをすることです
const Point3D = Object.assign(Object.create(Point), {
...
}
構文は特に好きではありませんが。
いつでもp = Object.create(Point)
をラップしてからp.init()
をコンストラクターにラップできます。例えばPoint.create(x,y)
。上記のコードを使用して、次の方法でPoint3D
"インスタンス"を作成できます。
var b = Point3D.create(1,2,3);
console.log(b); // { x:1, y:2, z:3 }
console.log(Point.isPrototypeOf(b)); // true
console.log(Point3D.isPrototypeOf(b)) // true
OLOOで静的メソッドをエミュレートするために、このハックを思いつきました。私はそれが好きかどうかわからない。 「静的」メソッドの先頭で特別なプロパティを呼び出す必要があります。たとえば、Point.create()
メソッドを静的にしました。
var p = Point.create(1,2);
var q = p.create(4,1); // Error!
または、ES6 Symbols を使用すると、JavaScriptベースクラスを安全に拡張できます。そのため、コードを保存して、Object.prototypeの特別なプロパティを定義できます。例えば、
const extendedJS = {};
( function(extension) {
const statics = Symbol('static');
Object.defineProperty(Object.prototype, statics, {
writable: true,
enumerable: false,
configurable: true,
value(obj, message) {
if (this !== obj)
throw Error(message);
}
});
Object.assign(extension, {statics});
})(extendedJS);
const Point = {
create (x, y) {
this[extendedJS.statics](Point);
...
@james emanon-それで、あなたは多重継承について言及しています(「JS:this&Object Prototypesを知らない」という本の75ページで説明しています)。そして、そのメカニズムは、たとえばアンダースコアの「拡張」機能で見つけることができます。あなたの例で述べたオブジェクトの名前は、リンゴ、オレンジ、キャンディーを少し混ぜたものですが、背後にあるポイントを理解しています。私の経験から、これはOOLOバージョンになります:
var ObjA = {
setA: function(a) {
this.a = a;
},
outputA: function() {
console.log("Invoking outputA - A: ", this.a);
}
};
// 'ObjB' links/delegates to 'ObjA'
var ObjB = Object.create( ObjA );
ObjB.setB = function(b) {
this.b = b;
}
ObjB.setA_B = function(a, b) {
this.setA( a ); // This is obvious. 'setA' is not found in 'ObjB' so by prototype chain it's found in 'ObjA'
this.setB( b );
console.log("Invoking setA_B - A: ", this.a, " B: ", this.b);
};
// 'ObjC' links/delegates to 'ObjB'
var ObjC = Object.create( ObjB );
ObjC.setC = function(c) {
this.c = c;
};
ObjC.setA_C = function(a, c) {
this.setA( a ); // Invoking 'setA' that is clearly not in ObjC shows that prototype chaining goes through ObjB all the way to the ObjA
this.setC( c );
console.log("Invoking setA_C - A: ", this.a, " C: ", this.c);
};
ObjC.setA_B_C = function(a, b, c){
this.setA( a ); // Invoking 'setA' that is clearly not in ObjC nor ObjB shows that prototype chaining got all the way to the ObjA
this.setB( b );
this.setC( c );
console.log("Invoking setA_B_C - A: ", this.a, " B: ", this.b, " C: ", this.c);
};
ObjA.setA("A1");
ObjA.outputA(); // Invoking outputA - A: A1
ObjB.setA_B("A2", "B1"); // Invoking setA_B - A: A2 B: B1
ObjC.setA_C("A3", "C1"); // Invoking setA_C - A: A3 C: C1
ObjC.setA_B_C("A4", "B2", "C1"); // Invoking setA_B_C - A: A4 B: B2 C: C1
簡単な例ですが、示されている点は、オブジェクトをかなりフラットな構造/フォーメーションでつなぎ合わせているだけで、複数のオブジェクトのメソッドとプロパティを使用できる可能性があることです。クラス/「プロパティのコピー」アプローチと同じことを達成します。カイルによる集計(114ページ、「this&Object Prototypes」):
つまり、実際のメカニズム、つまりJavaScriptで活用できる機能にとって重要なのは、他のオブジェクトにリンクされているオブジェクトについてです。
あなたにとってより自然な方法は、チェーン全体をモデル化するのではなく、すべての「親」(注意:))オブジェクトを1つの場所/関数呼び出しで述べることであることを理解しています。
それに必要なのは、それに応じてアプリケーションの問題を考え、モデル化することです。私もそれに慣れています。それが助けて、カイル自身からの最終評決が素晴らしいことを願っています。 :)