Kyle Simpson的“OLOO(链接到其他对象的对象)模式”与原型设计模式有什么不同?除了通过特别指示“链接”(原型的行为)的东西来创造它并澄清这里没有“复制”(类的行为),他的模式究竟引入了什么?
这是他的书中的an example of Kyle's pattern,“你不懂JS:这个和对象的原型”:
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."
答案 0 :(得分:140)
他的模式到底引入了什么?
OLOO按原样包含原型链,无需在其他(IMO混淆)语义上进行分层以获得链接。
因此,这两个片段具有完全相同的结果,但以不同的方式实现。
构造函数表单:
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
),而该对象又链接到第三个对象({{ 1}}或Foo.prototype
)。
片段之间的关系和委派是相同的。片段之间的内存使用情况相同。创建许多“子”(也就是FooObj
到x1
之类的许多对象等)的能力在片段之间是相同的。代理(x1000
和x.y
)的性能在代码段之间是相同的。 OLOO的对象创建性能 较慢,但sanity checking that表明性能较慢并不是问题。
我认为OLOO提供的是,表达对象并直接链接它们要比通过构造函数/ x.z
机制间接链接它们简单得多。后者假装是关于类的,但实际上只是表达委派的一种可怕的语法(旁注: ES6 new
语法也是如此!)。
OLOO正在切断中间人。
这是class
与OLOO的another comparison。
答案 1 :(得分:24)
我读过Kyle的书,我发现它确实很有用,特别是有关this
如何约束的详细信息。
对我来说,有一些OLOO的大职业选手:
OLOO依靠Object.create()
创建一个新对象[[prototype]]
- 链接到另一个对象。您不必理解函数具有prototype
属性或担心其修改可能带来的任何潜在缺陷。
这是有争议的,但我觉得OLOO语法(在很多情况下)比“标准”更简洁,更简洁。 javascript方法,特别是涉及多态(super
- 样式调用)。
我认为有一个有问题的设计(实际上有助于上面的第2点),这与阴影有关:
在行为授权中,我们避免在
[[Prototype]]
链的不同级别上尽可能地命名相同的东西。
这背后的想法是对象有自己更具体的功能,然后内部委托给链下游的功能。例如,您可能有一个resource
对象,其上有一个save()
函数,该对象将该对象的JSON版本发送到服务器,但您可能还有一个clientResource
对象,该对象具有stripAndSave()
函数,它首先删除不应发送到服务器的属性。
潜在的问题是:如果其他人出现并决定制作一个specialResource
对象,而不是完全了解整个原型链,他们可能会合理地*决定为属性下的最后一次保存保存时间戳名为save
,它隐藏了save()
对象上的基本resource
功能,在原型链中有两个链接:
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!
这是一个特别人为的例子,但问题是,特别是不阴影其他属性可能导致一些尴尬的情况和大量使用同义词库!
或许更好地说明这个问题的方法是init
方法 - 尤其是OOLO会回避构造函数类型函数。由于每个相关对象都可能需要这样的功能,因此对它们进行适当的命名可能是一项繁琐的工作,而且唯一性可能使得难以记住使用哪个。
*实际上它并不是特别合理(lastSaved
会好得多,但它只是一个例子。)
答案 2 :(得分:13)
“你不了解JS:这个和对象原型”中的讨论和OLOO的演示是发人深省的,我已经学到了很多东西。 OLOO模式的优点在其他答案中有详细描述;但是,我有以下宠物抱怨(或者我错过了阻止我有效应用它的东西):
<强> 1 强>
当经典模式中的“类”“继承”另一个“类”时,可以将这两个函数声明为类似语法("function declaration" or "function statement"):
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模式中,创建对象有两个步骤:
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模式中,对象实例上也可以使用任何“静态”函数(通过[[prototype]]链):
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));}
};
答案 3 :(得分:5)
&#34;我想这样做会使每个obj依赖于另一个&#34;
当Kyle解释两个对象[[Prototype]]
链接时,它们并非如此
相互依赖;相反,它们是个体对象。你正在链接一个
使用[[Prototype]]
链接对象,您可以随时更改。 如果您将通过OLOO样式创建的两个[[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
个对象通过任意对象相互链接
他们的constructor
函数(Foo.prototype
,Bar.prototype
,Baz.prototype
),但在前者(OLOO
样式)中,它们是直接链接的。这两种方式都只是将foo
,bar
,baz
彼此直接关联,直接在前者中,间接在后者中。但是,在这两种情况下,对象都是彼此独立的,因为它不像任何曾经实例化的类的实例,不能从其他类继承。您始终可以更改对象应该委托的对象。
var anotherObj= {};
Object.setPrototypeOf(foo, anotherObj);
所以他们都是彼此独立的。
&#34;我希望
OLOO
可以解决每个对象都知道的问题 没有关于另一个。&#34;
是的,确实可能 -
让我们使用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
- 样式中,一个对象委托给Tech
(明确设置了委托),而在constructor
- 样式中,一个对象委托给Tech.prototype
(委托是隐式设置的)。最终,您最终将三个对象(彼此之间没有链接)链接到一个对象,直接使用OLOO
- 样式,间接使用constructor
- 样式。
&#34;原样,ObjB必须从ObjA创建.. Object.create(ObjB)等&#34;
不,ObjB
这里不像任何类的实例(基于经典语言)
ObjA
。 可以说像objB
对象被委托给ObjA
对象创建它
时间&#34; 即可。如果您使用了构造函数,那么您可以使用.prototype
来间接地执行相同的耦合操作。
答案 4 :(得分:3)
@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__ = ...
越皱眉头了。我们现在也可以在常规对象上使用super,如Point3D.init()
中所示。另一种方法是做一些像
const Point3D = Object.assign(Object.create(Point), {
...
}
但是我并不特别喜欢语法。 我们总是可以将p = Object.create(Point)
然后p.init()
包装到构造函数中。例如Point.create(x,y)
。使用上面的代码,我们可以创建一个Point3D
&#34;实例&#34;以下列方式。
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
我想出了这个hack来模拟OLOO中的静态方法。我不确定我是否喜欢它。它需要在任何&#34;静态&#34;的顶部调用一个特殊属性。方法。例如,我已将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);
...
答案 5 :(得分:2)
@james emanon - 所以,你指的是多重继承(在第37页的书中讨论过#34;你不知道JS:这个&对象原型&#34;)。我们可以在下划线中找到这种机制&#34;扩展&#34;例如,功能。您在示例中说明的对象名称有点混合苹果,橙子和糖果,但我理解背后的观点。根据我的经验,这将是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
这是一个简单的例子,但显示的是我们只是将对象链接在一起,形成相当扁平的结构/形式,并且仍然可以使用来自多个对象的方法和属性。我们实现与类/&#34相同的东西;复制属性&#34;做法。 Kyle总结(第114页,&#34;此&amp; Object Prototypes&#34;):
换句话说,实际的机制,重要的是什么的本质 我们可以在JavaScript中使用的功能是所有关于对象的功能 被链接到其他对象。
我明白,更自然的方式是陈述所有的父母&#34; (小心:))对象在一个地方/函数调用而不是建模整个链。
它需要的是根据这一点在我们的应用程序中改变思维和建模问题。我也习惯了。希望它有所帮助,凯尔本人的最终判决将是伟大的。 :)
答案 6 :(得分:-1)
@Marcus,就像你一样,我一直热衷于OLOO,也不喜欢你的第一点所描述的不对称性。我一直在玩抽象,以恢复对称性。您可以创建一个link()
函数来代替Object.create()
。使用时,您的代码可能看起来像这样......
var Point = {
init : function(x,y) {
this.x = x;
this.y = y;
}
};
var Point3D = link(Point, {
init: function(x,y,z) {
Point.init.call(this, x, y);
this.z = z;
}
});
请记住,Object.create()
有第二个可以传入的参数。这是利用第二个参数的链接函数。它还允许一些自定义配置...
function link(delegate, props, propsConfig) {
props = props || {};
propsConfig = propsConfig || {};
var obj = {};
Object.keys(props).forEach(function (key) {
obj[key] = {
value: props[key],
enumerable: propsConfig.isEnumerable || true,
writable: propsConfig.isWritable || true,
configurable: propsConfig.isConfigurable || true
};
});
return Object.create(delegate, obj);
}
当然,我认为@Kyle不支持在Point3D对象中隐藏init()
函数。 ; - )
答案 7 :(得分:-1)
有没有办法让OLOO超过“两个”对象..所有的例子我都是基于基础的例子(参见OP的例子)。假设我们有以下对象,我们如何创建一个具有“其他”三个属性的“第四”对象? ALA ...
var Button = {
init: function(name, cost) {
this.buttonName = name;
this.buttonCost = cost;
}
}
var Shoe = {
speed: 100
}
var Bike = {
range: '4 miles'
}
这些对象是任意的,可以包含各种行为。但要点是,我们有'n'个对象,而我们的新对象需要三个方面的东西。
而不是给定的例子ala:
var newObj = Object.create(oneSingularObject);
newObj.whatever..
但是,我们的newObject =(按钮,自行车,鞋子......)
在OLOO中实现这一目标的模式是什么?