Kyle Simpson 的 OLOO 模式与原型设计模式
Kyle Simpson's OLOO Pattern vs Prototype Design Pattern
Kyle Simpson 的 "OLOO (Objects Linking to Other Objects) Pattern" 与 Prototype 设计模式有何不同?除了通过具体指示 "linking"(原型的行为)并澄清这里没有 "copying" 发生(类 的行为)之外,他的模式到底介绍了什么?
这是他的书 an example of Kyle's pattern "You Don't Know 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."
what exactly does his pattern introduce?
OLOO 按原样包含原型链,无需叠加其他(IMO 令人困惑的)语义来获得 linkage。
因此,这两个片段具有完全相同的结果,但到达那里的方式不同。
构造函数形式:
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]]
-linked 到一个对象(Bar.prototype
或 BarObj
),而后者又是linked 到第三个对象(Foo.prototype
或 FooObj
)。
片段之间的关系和委派是相同的。片段之间的内存使用情况是相同的。创建许多 "children"(又名,许多对象,如 x1
到 x1000
等)的能力在片段之间是相同的。委托(x.y
和 x.z
)的性能在片段之间是相同的。 OLOO 的对象创建性能 较慢,但 sanity checking that 表明性能较慢确实不是问题。
我认为 OLOO 提供的是,仅表达对象并直接 link 它们比通过构造函数/new
机制间接 link 它们要简单得多。后者假装是关于 类 但实际上只是表达委托的糟糕语法( 旁注: ES6 class
语法也是如此!)。
OLOO就是去掉了中间人
这是 class
与 OLOO 的 another comparison。
我读了 Kyle 的书,我发现它非常有用,尤其是关于如何绑定 this
的细节。
优点:
对我来说,OLOO有几个大优点:
1。简单
OLOO 依靠 Object.create()
创建一个新对象,该对象 [[prototype]]
链接到另一个对象。您不必了解函数具有 prototype
属性 或担心任何来自其修改的潜在相关陷阱。
2。更简洁的语法
这是有争议的,但我觉得 OLOO 语法(在许多情况下)比 'standard' javascript 方法更简洁、更简洁,尤其是在涉及多态时(super
式调用)。
缺点:
我认为设计中有一点值得怀疑(实际上对上述第 2 点有贡献),那就是与阴影有关:
In behaviour delegation, we avoid if at all possible naming things the same at different levels of the [[Prototype]]
chain.
这背后的想法是,对象有自己更具体的功能,然后在内部委托给链下游的功能。例如,您可能有一个带有 save()
函数的 resource
对象,该函数将对象的 JSON 版本发送到服务器,但您也可能有一个 clientResource
具有 stripAndSave()
函数的对象,该函数首先删除不应发送到服务器的属性。
潜在的问题是:如果其他人出现并决定制作一个 specialResource
对象,而不完全了解整个原型链,他们可能合理地*决定为最后一次保存保存时间戳一个名为 save
的 属性,它隐藏了 resource
对象上的基础 save()
功能,原型链下的两个链接:
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
会好很多,只是举个例子)
"You Don't Know JS: this & Object Prototypes" 中的讨论和 OLOO 的介绍发人深省,我通过这本书学到了很多东西。 OLOO 模式的优点在其他答案中有详细描述;但是,我对它有以下宠物抱怨(或者缺少一些阻止我有效应用它的东西):
1
当一个"class""inherits"另一个"class"在经典模式中,这两个函数可以声明类似的语法("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);
相比之下,在原型模式中,您使用标准运算符 new
:
var p2a = new Point(1,1);
3
在经典模式中,我可以通过将它们直接分配给 "class" 函数(与其 .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 模式中,对象实例上的任何 "static" 函数也可用(通过 [[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));}
};
@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()
有一个可以传入的第二个参数。这里是利用第二个参数的 link 函数。它还允许一些自定义配置...
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()
函数。 ;-)
有没有办法让 OLOO 超过 "two" 个对象.. 我包含的所有示例都是基于示例的(请参阅 OP 的示例)。假设我们有以下对象,我们如何创建一个具有 'other' 三个属性的 "fourth" 对象?阿拉...
var Button = {
init: function(name, cost) {
this.buttonName = name;
this.buttonCost = cost;
}
}
var Shoe = {
speed: 100
}
var Bike = {
range: '4 miles'
}
这些对象是任意的,可以包含各种行为。但要点是,我们有 'n' 个对象,我们的新对象需要这三个对象的一些东西。
而不是给定的例子:
var newObj = Object.create(oneSingularObject);
newObj.whatever..
但是,我们的 newObject = (Button, Bike, Shoe)......
在 OLOO 中实现这一目标的模式是什么?
@james emanon - 所以,你指的是多重继承(在书 "You Don't Know JS: this & Object Prototypes" 的第 75 页上讨论)。例如,我们可以在下划线的 "extend" 函数中找到该机制。您在示例中陈述的对象名称有点混合了苹果、橙子和糖果,但我理解背后的意义。根据我的经验,这将是 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
这是一个简单的示例,但要点是我们只是将对象以相当平坦的方式链接在一起 structure/formation,并且仍然有可能使用来自多个对象的方法和属性。我们实现了与 class/"copying the properties" 方法相同的效果。凯尔总结(第 114 页,"this & Object Prototypes"):
In other words, the actual mechanism, the essence of what’s important
to the functionality we can leverage in JavaScript, is all about objects
being linked to other objects.
我知道对您来说更自然的方法是在一个 place/function 调用中声明所有 "parent"(小心 :))对象,而不是对整个链进行建模。
它需要的是根据这一点转变我们应用程序中的思维和建模问题。我也在慢慢习惯。希望它能有所帮助,凯尔本人的最终裁决会很棒。 :)
"I figured to do it makes each obj dependent on the other"
正如 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
后者与前者的唯一区别b/w是后者的代码
foo
、bar
、baz
bbjects 通过任意对象相互链接
它们的 constructor
功能(Foo.prototype
、Bar.prototype
、Baz.prototype
),但在前一个(OLOO
样式)中,它们是直接链接的。这两种方式都只是将 foo
、bar
、baz
彼此链接起来,前者直接链接,后者间接链接。但是,在这两种情况下,对象都是相互独立的,因为它不像任何 class 的实例,一旦实例化,就不能从其他 class 继承。您也可以随时更改一个对象应该委托给哪个对象。
var anotherObj= {};
Object.setPrototypeOf(foo, anotherObj);
所以他们都是相互独立的。
" I was hoping OLOO
would solve the issue in which each object knows
nothing about the other."
是的,这确实是可能的-
让我们使用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
-style,间接使用 constructor
-style.
"As is, ObjB has to be created from ObjA.. Object.create(ObjB) etc"
不,ObjB
这里不像任何 class 的实例(在 class 基于 ical 的语言中)
ObjA
。 可以说 objB
对象在创建时被委托给 ObjA
对象
时间”。如果你使用构造函数,你会做同样的事情 'coupling',虽然间接地通过使用 .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__ = ...
越皱眉。我们现在也可以在常规对象上使用 super,如 Point3D.init()
所示。另一种方法是做类似
const Point3D = Object.assign(Object.create(Point), {
...
}
虽然我不是特别喜欢语法。
我们总是可以将 p = Object.create(Point)
和 p.init()
包装到构造函数中。例如Point.create(x,y)
。使用上面的代码,我们可以按以下方式创建 Point3D
"instance"。
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 中的静态方法。我不确定我喜不喜欢。它需要在任何 "static" 方法的顶部调用一个特殊的 属性。例如,我已将 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);
...
Kyle Simpson 的 "OLOO (Objects Linking to Other Objects) Pattern" 与 Prototype 设计模式有何不同?除了通过具体指示 "linking"(原型的行为)并澄清这里没有 "copying" 发生(类 的行为)之外,他的模式到底介绍了什么?
这是他的书 an example of Kyle's pattern "You Don't Know 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."
what exactly does his pattern introduce?
OLOO 按原样包含原型链,无需叠加其他(IMO 令人困惑的)语义来获得 linkage。
因此,这两个片段具有完全相同的结果,但到达那里的方式不同。
构造函数形式:
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]]
-linked 到一个对象(Bar.prototype
或 BarObj
),而后者又是linked 到第三个对象(Foo.prototype
或 FooObj
)。
片段之间的关系和委派是相同的。片段之间的内存使用情况是相同的。创建许多 "children"(又名,许多对象,如 x1
到 x1000
等)的能力在片段之间是相同的。委托(x.y
和 x.z
)的性能在片段之间是相同的。 OLOO 的对象创建性能 较慢,但 sanity checking that 表明性能较慢确实不是问题。
我认为 OLOO 提供的是,仅表达对象并直接 link 它们比通过构造函数/new
机制间接 link 它们要简单得多。后者假装是关于 类 但实际上只是表达委托的糟糕语法( 旁注: ES6 class
语法也是如此!)。
OLOO就是去掉了中间人
这是 class
与 OLOO 的 another comparison。
我读了 Kyle 的书,我发现它非常有用,尤其是关于如何绑定 this
的细节。
优点:
对我来说,OLOO有几个大优点:
1。简单
OLOO 依靠 Object.create()
创建一个新对象,该对象 [[prototype]]
链接到另一个对象。您不必了解函数具有 prototype
属性 或担心任何来自其修改的潜在相关陷阱。
2。更简洁的语法
这是有争议的,但我觉得 OLOO 语法(在许多情况下)比 'standard' javascript 方法更简洁、更简洁,尤其是在涉及多态时(super
式调用)。
缺点:
我认为设计中有一点值得怀疑(实际上对上述第 2 点有贡献),那就是与阴影有关:
In behaviour delegation, we avoid if at all possible naming things the same at different levels of the
[[Prototype]]
chain.
这背后的想法是,对象有自己更具体的功能,然后在内部委托给链下游的功能。例如,您可能有一个带有 save()
函数的 resource
对象,该函数将对象的 JSON 版本发送到服务器,但您也可能有一个 clientResource
具有 stripAndSave()
函数的对象,该函数首先删除不应发送到服务器的属性。
潜在的问题是:如果其他人出现并决定制作一个 specialResource
对象,而不完全了解整个原型链,他们可能合理地*决定为最后一次保存保存时间戳一个名为 save
的 属性,它隐藏了 resource
对象上的基础 save()
功能,原型链下的两个链接:
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
会好很多,只是举个例子)
"You Don't Know JS: this & Object Prototypes" 中的讨论和 OLOO 的介绍发人深省,我通过这本书学到了很多东西。 OLOO 模式的优点在其他答案中有详细描述;但是,我对它有以下宠物抱怨(或者缺少一些阻止我有效应用它的东西):
1
当一个"class""inherits"另一个"class"在经典模式中,这两个函数可以声明类似的语法("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);
相比之下,在原型模式中,您使用标准运算符 new
:
var p2a = new Point(1,1);
3
在经典模式中,我可以通过将它们直接分配给 "class" 函数(与其 .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 模式中,对象实例上的任何 "static" 函数也可用(通过 [[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));}
};
@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()
有一个可以传入的第二个参数。这里是利用第二个参数的 link 函数。它还允许一些自定义配置...
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()
函数。 ;-)
有没有办法让 OLOO 超过 "two" 个对象.. 我包含的所有示例都是基于示例的(请参阅 OP 的示例)。假设我们有以下对象,我们如何创建一个具有 'other' 三个属性的 "fourth" 对象?阿拉...
var Button = {
init: function(name, cost) {
this.buttonName = name;
this.buttonCost = cost;
}
}
var Shoe = {
speed: 100
}
var Bike = {
range: '4 miles'
}
这些对象是任意的,可以包含各种行为。但要点是,我们有 'n' 个对象,我们的新对象需要这三个对象的一些东西。
而不是给定的例子:
var newObj = Object.create(oneSingularObject);
newObj.whatever..
但是,我们的 newObject = (Button, Bike, Shoe)......
在 OLOO 中实现这一目标的模式是什么?
@james emanon - 所以,你指的是多重继承(在书 "You Don't Know JS: this & Object Prototypes" 的第 75 页上讨论)。例如,我们可以在下划线的 "extend" 函数中找到该机制。您在示例中陈述的对象名称有点混合了苹果、橙子和糖果,但我理解背后的意义。根据我的经验,这将是 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
这是一个简单的示例,但要点是我们只是将对象以相当平坦的方式链接在一起 structure/formation,并且仍然有可能使用来自多个对象的方法和属性。我们实现了与 class/"copying the properties" 方法相同的效果。凯尔总结(第 114 页,"this & Object Prototypes"):
In other words, the actual mechanism, the essence of what’s important to the functionality we can leverage in JavaScript, is all about objects being linked to other objects.
我知道对您来说更自然的方法是在一个 place/function 调用中声明所有 "parent"(小心 :))对象,而不是对整个链进行建模。
它需要的是根据这一点转变我们应用程序中的思维和建模问题。我也在慢慢习惯。希望它能有所帮助,凯尔本人的最终裁决会很棒。 :)
"I figured to do it makes each obj dependent on the other"
正如 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
后者与前者的唯一区别b/w是后者的代码
foo
、bar
、baz
bbjects 通过任意对象相互链接
它们的 constructor
功能(Foo.prototype
、Bar.prototype
、Baz.prototype
),但在前一个(OLOO
样式)中,它们是直接链接的。这两种方式都只是将 foo
、bar
、baz
彼此链接起来,前者直接链接,后者间接链接。但是,在这两种情况下,对象都是相互独立的,因为它不像任何 class 的实例,一旦实例化,就不能从其他 class 继承。您也可以随时更改一个对象应该委托给哪个对象。
var anotherObj= {};
Object.setPrototypeOf(foo, anotherObj);
所以他们都是相互独立的。
" I was hoping
OLOO
would solve the issue in which each object knows nothing about the other."
是的,这确实是可能的-
让我们使用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
-style,间接使用 constructor
-style.
"As is, ObjB has to be created from ObjA.. Object.create(ObjB) etc"
不,ObjB
这里不像任何 class 的实例(在 class 基于 ical 的语言中)
ObjA
。 可以说 objB
对象在创建时被委托给 ObjA
对象
时间”。如果你使用构造函数,你会做同样的事情 'coupling',虽然间接地通过使用 .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__ = ...
越皱眉。我们现在也可以在常规对象上使用 super,如Point3D.init()
所示。另一种方法是做类似const Point3D = Object.assign(Object.create(Point), { ... }
虽然我不是特别喜欢语法。
我们总是可以将
p = Object.create(Point)
和p.init()
包装到构造函数中。例如Point.create(x,y)
。使用上面的代码,我们可以按以下方式创建Point3D
"instance"。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 中的静态方法。我不确定我喜不喜欢。它需要在任何 "static" 方法的顶部调用一个特殊的 属性。例如,我已将
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);
...