凯尔辛普森的OLOO模式vs原型devise模式
Kyle Simpson的“OLOO(对象链接到其他对象)模式”与Prototypedevise模式有什么不同吗? 除了用专门指出“连接”(原型行为)的东西来表明它并没有在这里发生的“复制”(类的行为)之外,他的模式到底是什么呢?
下面是他的书“你不知道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."
他的模式到底是什么?
OLOO原样包含原型链,无需在其他(IMO混淆)语义层上获取链接。
所以,这两个片段有相同的结果,但以不同的方式得到。
构造函数forms:
function Foo() {} Foo.prototype.y = 11; function Bar() {} Bar.prototype = Object.create(Foo.prototype); Bar.prototype.z = 31; var x = new Bar(); xy + xz; // 42
OLOOforms:
var FooObj = { y: 11 }; var BarObj = Object.create(FooObj); BarObj.z = 31; var x = Object.create(BarObj); xy + xz; // 42
在这两个片段中,一个x
对象是[[Prototype]]
到一个对象( Bar.prototype
或BarObj
),该对象又链接到第三个对象( Foo.prototype
或FooObj
)。
片段之间的关系和委派是相同的。 片段之间的内存使用情况相同。 创build许多“孩子”(又名许多对象,如x1
到x1000
等)的能力在片段之间是相同的。 代表团( xy
和xz
)的performance在片段之间是相同的。 OLOO的对象创build性能比较慢,但是理智的检查表明性能下降并不是问题。
我认为OLOO提供的仅仅是expression对象并直接链接它们要比通过构造器/ new
机制间接链接它们要简单得多。 后者假装是关于类的,但对于表示代表来说真的只是一个糟糕的语法( 注意: ES6 class
语法也是如此!)。
OLOO正在剪掉中间人。
这是另一个比较 class
与OLOO。
我读了凯尔的书,我发现它很有用,特别是关于this
是如何绑定的细节。
优点:
对我来说,有几个OLOO的大优点:
简单
OLOO依靠Object.create()
来创build一个新的对象,它是[[prototype]]
到另一个对象。 您不必了解函数具有prototype
属性,也不必担心其修改所带来的潜在相关缺陷。
2.清理语法
这是有争议的,但我觉得OLOO的语法(在许多情况下)比“标准”的JavaScript方法更整齐,更简洁,特别是当涉及到多态( super
调用)时。
缺点:
我认为有一个可疑的devise(实际上有助于上面第二点),那就是与阴影有关:
在行为授权中,我们尽可能地避免在
[[Prototype]]
链的不同级别命名事物。
这个背后的想法是,对象有自己的更具体的function,然后内部委托给链下的function。 例如,您可能拥有一个带有save()
函数的resource
对象,该对象将JSON版本的对象发送给服务器,但是您也可能有一个clientResource
stripAndSave()
函数的clientResource
对象,该对象首先会删除属性不应该被发送到服务器。
潜在的问题是:如果有其他人出现并决定创build一个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". a.timeStampedSave(); // Error!
这是一个特别人为的例子,但重要的是不要隐瞒其他属性会导致一些尴尬的情况和大量使用同义词库!
也许更好的例子是init
方法 – 特别是OOLO构造函数types的function。 由于每个相关对象都可能需要这样一个function,所以适当地命名它们可能是一个单调乏味的操作,并且唯一性可能使得难以记住要使用哪个对象。
*其实这不是特别合理( lastSaved
会好很多,但只是一个例子)。
在“你不知道JS:这个和对象原型”的讨论和OLOO的介绍是发人深省,我已经学会了通过这本书的吨。 OLOO模式的优点在其他答案中有很好的描述。 然而,我有以下的宠物投诉(或者错过了一些阻止我有效应用的东西):
1
当经典模式中的“类”“inheritance”另一个“类”时,可以将这两个函数声明为类似的语法( “函数声明”或“函数声明” ):
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模式中,用于定义基本和派生对象的不同语法forms:
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模式中,创build一个对象有两个步骤:
- 调用
Object.create
-
调用一些自定义,非标准的方法来初始化对象(你必须记住,因为它可能从一个对象到下一个对象):
var p2a = Object.create(Point); p2a.init(1,1);
相反,在原型模式中,您使用new
的标准运算符:
var p2a = new Point(1,1);
3
在古典模式中,我可以创build“静态”效用函数,通过直接将它们分配给“类”函数(而不是它的.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));} };
“我打算这样做,使每个obj依赖于其他”
正如凯尔所解释的,当两个对象是[[Prototype]]
关联的时候,它们并不是真的相互依赖; 相反,他们是个人的对象。 你用一个[[Prototype]]
链接把一个对象连接到另一个对象上,随时可以改变它。 如果你把通过OLOO风格创build的两个[[Prototype]]
链接对象作为相互依赖关系,那么你也应该考虑通过constructor
调用创build的对象。
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
,bazbbjects通过constructor
函数的任意对象( Foo.prototype
, Bar.prototype
, Baz.prototype
)链接到对方,但在前者( OLOO
风格)中则直接相连。 两种方法你只是把foo
, bar
, baz
直接连在一起,间接连在一起。 但是,在这两种情况下,这些对象是相互独立的,因为它们不像任何曾经实例化过的类的实例,不能从其他类inheritance。 你总是可以改变对象应该委托的对象。
var anotherObj= {}; Object.setPrototypeOf(foo, anotherObj);
所以他们都是彼此独立的
“我希望
OLOO
能够解决每个对象对另一个都一无所知的问题。”
是的,这确实是可能的 –
让我们使用Tech
作为实用对象 –
var Tech= { tag: "technology", setName= function(name) { this.name= name; } }
创build尽可能多的对象链接到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-
function Tech() { } Tech.prototype.tag= "technology"; Tech.prototype.setName= function(name) { this.name= name; }
创build尽可能多的对象,你想链接到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
风格。
“因为,ObjB必须从ObjA .. Object.create(ObjB)等创build”
不,这里的ObjB
不像任何一个类ObjA
的实例(在经典的语言中)。 可以这样说objB
对象是在创build的时候委托给ObjA
对象的“如果你使用了构造函数,尽pipe间接地通过使用.prototype
s,你也可以做同样的”耦合“。
@马库斯@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; } };
当然,创build一个链接到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)
。 使用上面的代码,我们可以通过以下方式创build一个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 符号,您可以安全地扩展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 – 所以,你指的是多重inheritance(在“你不知道JS:this&Object Prototypes”一书第75页讨论过)。 例如,我们可以在下划线的“扩展”function中find这种机制。 在你的例子中提到的对象的名字有点混合苹果,橘子和糖果,但我明白了背后的意思。 从我的经验来看,这将是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
这是一个简单的例子,但是要表明的一点是,我们只是将对象链接在一个相当扁平的结构/forms中,仍然有可能使用来自多个对象的方法和属性。 我们实现与类/“复制属性”方法相同的东西。 Kyle总结(第114页,“this&Object Prototypes”):
换句话说,实际的机制,对我们可以在JavaScript中利用的function来说重要的实质, 都是关于对象与其他对象的链接 。
我明白,对你来说更自然的方式是在一个地方/函数调用中声明所有的“父”(小心:))对象,而不是build模整个链。
它所要求的就是根据这个原理转换我们的应用程序的思维和build模问题。 我也习惯了。 希望这有助于凯尔本人的最终裁决。 🙂
@马库斯,就像你一样,我一直热衷于OLOO,也不喜欢你的第一点所描述的不对称。 我一直在玩弄抽象,以恢复对称。 您可以创build一个用于替代Object.create()
的link()
函数。 使用时,你的代码可能看起来像这样…
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()
具有可以传入的第二个参数。下面是使用第二个参数的链接函数。 它也允许一点点的自定义configuration…
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不会支持Shadow3D对象中的init()
函数。 😉
有没有比OLOO更多的方法,所有的例子都是基于这个例子(参见OP的例子)。 假设我们有以下的对象,我们如何创build一个具有“其他”三个属性的“第四个”对象? 翼…
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 =(Button,Bike,Shoe)……
在OLOO中进行这种操作的模式是什么?