Kyle Simpson 的 OLOO 模式与原型设计模式

Kyle Simpson 的“ OLOO (对象链接到其他对象)模式”与原型设计模式有什么不同吗?除了通过明确指出“链接”(原型的行为)和澄清这里没有发生“复制”(类的行为)之外,他的模式到底引入了什么?

Here's 凯尔模式的一个例子 from his book, "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."
28591 次浏览

他的模式到底引出了什么?

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.prototypeBarObj) ,而后者又链接到第三个对象(Foo.prototypeFooObj)。

代码段之间的关系和委托是相同的。代码段之间的内存使用情况是相同的。在代码片段之间创建许多“子代”(也就是许多对象,如 x1x1000等)的能力是相同的。代理的性能(x.yx.z)在代码段之间是相同的。使用 OLOO 时,对象创建性能 较慢,但是 检查一下你是否神志清醒表明性能较慢真的不是问题。

我认为 OLOO 提供的是,仅仅表示对象并直接链接它们,比通过构造函数/new机制间接链接它们要简单得多。后者假装是关于类的,但实际上只是表达委托的糟糕语法(附注:也是 ES6class语法!).

OLOO is just cutting out the middle-man.

这是 class和 OLOO 的 另一个比较

我读了凯尔的书,我发现它真的很有用,特别是关于 this是如何绑定的细节。

优点:

对我来说,OLOO 有两大优点:

1. 简单

OLOO 依赖于 Object.create()来创建一个新的对象,该对象是 [[prototype]]链接到另一个对象的。您不必了解函数具有 prototype属性,也不必担心修改函数会带来任何潜在的相关陷阱。

2. Cleaner syntax

这是有争议的,但是我觉得 OLOO 语法(在很多情况下)比“标准”javascript 方法更简洁,特别是在多态性(super风格的调用)方面。

缺点:

我认为有一点设计是值得怀疑的(一点实际上有助于上面第2点) ,那就是阴影:

在行为委托中,我们尽可能避免在 [[Prototype]]链的不同层次上使用相同的命名。

这背后的想法是,对象有自己更具体的功能,然后在内部委托给下游的功能。例如,你可能有一个带有 save()函数的 resource对象,它向服务器发送对象的 JSON 版本,但是你也可能有一个带有 stripAndSave()函数的 clientResource对象,它首先删除不应该发送到服务器的属性。

潜在的问题是: 如果其他人决定创建一个 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会更好,但它只是一个例子。)

在“你不知道 JS: 这 & 对象原型”的讨论和 OLOO 的介绍是发人深省的,我已经通过这本书学到了很多。OLOO 模式的优点在其他答案中得到了很好的描述; 然而,我对它有以下的抱怨(或者我缺少了一些阻止我有效应用它的东西) :

1

当一个“ class”“继承”经典模式中的另一个“ class”时,这两个函数可以被声明为相似的语法(“函数声明”或“函数语句”) :

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 模式中,创建一个对象有两个步骤:

  1. 呼叫 Object.create
  2. 调用一些自定义的、非标准的方法来初始化对象(你必须记住这一点,因为它可能随着对象的不同而不同) :

     var p2a = Object.create(Point);
    
    
    p2a.init(1,1);
    

In contrast, in the Prototype pattern you use the standard operator 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 模式中,对象实例上也可以使用任何“静态”函数(通过[[原型]]链) :

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));}
};

@ 马库斯,和你一样,我也很喜欢 OLOO 也不喜欢你第一点所描述的不对称。我一直在尝试抽象概念来恢复对称性。您可以创建一个用于代替 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()还有一个可以传入的参数。下面是利用第二个参数的 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 超过“两个”对象。.我所有的例子都包含了基本的例子(参见 OP 的例子)。假设我们有以下对象,我们如何创建具有“其他”三个属性的“第四个”对象?Ala....

var Button = {
init: function(name, cost) {
this.buttonName = name;
this.buttonCost = cost;
}
}


var Shoe = {
speed: 100
}


var Bike = {
range: '4 miles'
}

这些对象是任意的,可以包含各种行为。但是要点是,我们有 n 个对象,我们的新对象需要从这三个对象中得到一些东西。

而不是给出的例子 a:

var newObj = Object.create(oneSingularObject);
newObj.whatever..

但是,我们的新对象 = (按钮,自行车,鞋) ..。

What is the pattern to get this going in OLOO?

@ james emanon-所以,你指的是多重继承(在《你不知道 JS: this & Object Prototype 》一书的第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

这是一个简单的例子,但是要说明的是,我们只是将对象以相当平坦的结构/形式链接在一起,并且仍然有可能使用来自多个对象的方法和属性。我们实现了与类/“复制属性”方法相同的功能。Kyle 总结(第114页,“ this & Object Prototype”) :

换句话说,实际的机制,什么是重要的本质 to the functionality we can leverage in JavaScript, is all about objects 被链接到其他对象

我理解更自然的方法是在一个位置/函数调用中声明所有的“父”(小心:)对象,而不是建模整个链。

它所需要的是根据这一点在我们的应用程序中转换思维和建模问题。我也习惯了。希望能有所帮助,凯尔本人的最终裁决会很棒。:)

“我认为这样做会使每个对象都依赖于另一个”

正如 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 barbaz是相互依赖的吗?

现在让我们做同样的 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 后者和前者的代码是在后者 foobarbaz对象通过任意对象相互链接 它们的 constructor功能(Foo.prototypeBar.prototypeBaz.prototype) ,但在前者(OLOO风格)中它们是直接连接的。这两种方法都只是将 foobarbaz相互连接起来,直接连接在前一种方法中,间接连接在后一种方法中。但是,在这两种情况下,对象是相互独立的,因为它实际上不像任何类的实例,一旦实例化,就不能从其他类继承。您也可以随时更改对象应该委托的对象。

var anotherObj= {};
Object.setPrototypeOf(foo, anotherObj);

So they're all independent of each-other.

我希望 OLOO能解决每个物体都知道的问题 没有关于其他人的。”

是的,这确实是可能的-

让我们使用 Tech作为一个实用对象-

 var Tech= {
tag: "technology",
setName= function(name) {
this.name= name;
}
}

create as many objects as you wish linked to 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

你认为 htmlcssjs对象是相互连接的吗?不,他们不是。现在让我们看看如何做到这一点与 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风格的对象(htmlcssjs) 对象不同于 OLOO样式的代码?事实上,它们的目的是一样的。在 OLOO风格中,一个对象委托给 Tech(委托是显式设置的) ,而在 constructor风格中,一个对象委托给 Tech.prototype(委托是隐式设置的)。最终,您将这三个对象链接起来,彼此之间没有链接,直接使用 OLOO风格,间接使用 constructor风格。

“按照目前的情况,必须从 ObjecA. . Object.create (ObjecB)等创建 ObjecB”

不,这里的 ObjB不像任何类的实例(在基于古典语言的语言中) 应该说,就像 objB对象在创建时被委托给 ObjA对象一样 如果您使用构造函数,您将执行相同的“耦合”操作,尽管是通过使用 .prototypes 间接执行的

@ 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 对象是有点傻,但这不是重点(我想与您的示例保持一致)。不管怎样,就抱怨而言:

  1. 不对称可以用 ES6的 Object.setPrototypeOf或 the more frowned upon __proto__ = ... that I use. We can also use 好极了 on regular objects now too, as seen in Point3D.init(). Another way would be to do something like

    const Point3D = Object.assign(Object.create(Point), {
    ...
    }
    

    虽然我不太喜欢这句话的语法。


  1. 我们总是可以将 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
    

  1. I just came up with this hack to emulate static methods in OLOO. I'm not sure if I like it or not. It requires calling a special property at the top of any "static" methods. For example, I've made the Point.create() method static.

        var p = Point.create(1,2);
    var q = p.create(4,1);          // Error!
    

Alternatively, with ES6 Symbols you can safely extend Javascript base classes. So you could save yourself some code and define the special property on Object.prototype. For example,

    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);
...