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

shm*_*uli 102 javascript design-patterns

Kyle Simpson的"OLOO(链接到其他对象的对象)模式"与原型设计模式有何不同?除了通过专门指示"链接"(原型的行为)的东西创造它并澄清这里没有"复制"(类的行为),他的模式究竟引入了什么?

以下是Kyle在他的书"你不懂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."
Run Code Online (Sandbox Code Playgroud)

Kyl*_*son 144

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

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
Run Code Online (Sandbox Code Playgroud)

OLOO表格:

var FooObj = { y: 11 };

var BarObj = Object.create(FooObj);
BarObj.z = 31;

var x = Object.create(BarObj);
x.y + x.z;  // 42
Run Code Online (Sandbox Code Playgroud)

在两个片段中,x对象都链接到[[Prototype]]对象(Bar.prototypeBarObj),而对象又链接到第三个对象(Foo.prototypeFooObj).

片段之间的关系和委托是相同的.片段之间的内存使用情况相同.在片段之间创建许多"孩子"(也就是诸如x1通过x1000等许多对象)的能力是相同的.委托(x.yx.z)的性能在片段之间是相同的.创建对象的性能与0100比较慢,但理智检查表明,较慢的性能真的不是一个问题.

我认为OLOO提供的是,表达对象并直接链接它们要比通过构造函数/ new机制间接链接它们简单得多.后者假装是关于类,但实际上只是表达委派的可怕语法(旁注: ES6 class语法也是如此!).

OLOO正在切断中间人.

这是vs OLOO 的另一个比较class.

  • 我想补充一点,现在,仅仅一年多之后,Object.create()在chrome中进行了大量优化,并且jsperf显示它 - 它是现在最快的选项之一.这显示了为什么你不应该关注这种微优化,而只是编写算法健全的代码. (6认同)
  • @Pier的性能实际上并不是那么大的问题.修复了关于完整性检查对象创建性能的破坏的博客帖子链接,这解释了如何正确地考虑这一点. (3认同)
  • 我发现你的答案非常有趣,并且你的书中描述了OLOO的想法,我希望得到你对这个问题的反馈:http://stackoverflow.com/questions/40395762/oloo-how-to-access-private-variable如果您发现此实现正确以及如何解决与访问私有成员相关的问题.感谢您的提前时间,并祝贺您的​​最新着作. (2认同)
  • 并且jQuery比DOM API慢,对吧?但是,这是今年,男人 - 我宁愿优雅而简单地写,而不是担心优化.如果我需要稍后进行微量优化,我会在时机成熟时担心. (2认同)

Ed *_*ffe 24

我读了凯尔的书,我发现它确实很有用,特别是关于如何this约束的细节.

优点:

对我来说,有两个OLOO的大职业选手:

1.简单

OLOO依赖于Object.create()创建一个[[prototype]]与另一个对象链接的新对象.你不必理解函数有一个prototype属性或担心其修改可能带来的任何潜在缺陷.

2.更清晰的语法

这是有争议的,但我觉得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!
Run Code Online (Sandbox Code Playgroud)

这是一个特别人为的例子,但重点是,特别是遮蔽其他属性可能会导致一些尴尬的情况和大量使用同义词库!

或许更好地说明这种init方法 - 特别是因为OOLO回避构造函数类型函数而感到痛苦.由于每个相关对象都可能需要这样的功能,因此对它们进行适当命名可能是一项繁琐的工作,并且唯一性可能使得难以记住使用哪个.

*实际上它并不是特别合理(lastSaved会更好,但它只是一个例子.)

  • 我同意名称冲突的可能性是一个缺点......但实际上它是`[[Prototype]]`系统本身的缺点,而不是OLOO. (21认同)

Mar*_*tus 13

"你不了解JS:这个和对象原型"中的讨论以及OLOO的演示是发人深省的,我已经学到了很多东西.OLOO模式的优点在其他答案中有详细描述; 但是,我有以下宠物抱怨(或者我遗漏了一些阻止我有效应用它的东西):

1

当一个"类"在经典模式中"继承"另一个"类"时,这两个函数可以被声明为类似的语法("函数声明"或"函数声明"):

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);
Run Code Online (Sandbox Code Playgroud)

相反,在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;
};
Run Code Online (Sandbox Code Playgroud)

正如您在上面的示例中所看到的,基础对象可以使用对象文字表示法定义,而相同的表示法不能用于派生对象.这种不对称性让我感到困惑.

2

在OLOO模式中,创建对象有两个步骤:

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

     var p2a = Object.create(Point);
    
     p2a.init(1,1);
    
    Run Code Online (Sandbox Code Playgroud)

相反,在Prototype模式中,您使用标准运算符new:

var p2a = new Point(1,1);
Run Code Online (Sandbox Code Playgroud)

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));
};
Run Code Online (Sandbox Code Playgroud)

相反,在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));}
};
Run Code Online (Sandbox Code Playgroud)

  • 您的第一个代码示例中没有文字.你可能会滥用"字面"一词来赋予它另一种含义.只是说...... (2认同)
  • 关于第二点,作者认为将创建和初始化分开是一个“更好的”关注点分离,并引用了一些可能会发光的罕见用例(例如对象池)。我觉得这个论点非常无力。 (2认同)
  • 关于第二点,再次使用OLOO,您可以一次创建对象,然后等待初始化,而对于构造函数,则必须在创建时进行初始化,因此Kyle认为这是一个好处。 (2认同)

Abh*_*han 5

"我想这样做会让每个obj依赖于另一个"

正如凯尔解释当两个物体[[Prototype]]相连时,它们并不真正相互依赖; 相反,它们是个体对象.您可以通过链接将一个对象链接到另一个对象[[Prototype]],您可以随时更改该链接.如果将[[Prototype]]通过OLOO样式创建的两个链接对象相互依赖,则还应该对通过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
Run Code Online (Sandbox Code Playgroud)

现在考虑一下你是否想到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
Run Code Online (Sandbox Code Playgroud)

的B/W后者和前者的代码是在后者的一个唯一的区别 foo,bar,bazbbjects通过它们的任意对象链接到每个-其他constructor功能(Foo.prototype,Bar.prototype,Baz.prototype),但在前者(OLOO风格)它们直接相连.你只是连接两种方式foo,bar,baz相互直接在前一个和间接后者.但是,在这两种情况下,对象彼此独立,因为它实际上不像曾经实例化的任何类的实例,不能从其他类继承.您始终可以更改对象应该委派的对象.

var anotherObj= {};
Object.setPrototypeOf(foo, anotherObj);
Run Code Online (Sandbox Code Playgroud)

所以他们都是彼此独立的.

"我希望OLOO能够解决每个对象对另一个都一无所知的问题."

是的,这确实可能 -

让我们Tech用作实用对象 -

 var Tech= {
     tag: "technology",
     setName= function(name) {
              this.name= name;
}
}
Run Code Online (Sandbox Code Playgroud)

根据您的意愿创建任意数量的对象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
Run Code Online (Sandbox Code Playgroud)

你认为html,css,js对象相互连接,其他的?不,他们不是.现在让我们看看我们如何通过constructor功能实现这一目标 -

function Tech() { }

Tech.prototype.tag= "technology";

Tech.prototype.setName=  function(name) {
              this.name= name;
}
Run Code Online (Sandbox Code Playgroud)

根据您的意愿创建任意数量的对象Tech.proptotype-

var html= new Tech(),
     css= new Tech(),
      js= new Tech();
Run Code Online (Sandbox Code Playgroud)

一些检查(避免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
Run Code Online (Sandbox Code Playgroud)

您如何看待这些constructor风格对象(html,css,js)对象从不同OLOO风格的代码?事实上,它们服务于同一目的.In- OLOOstyle one对象委托给Tech(明确设置委托),而in- constructorstyle one对象委托给Tech.prototype(委托被隐式设置).最终,您最终将三个对象(彼此之间没有链接)链接到一个对象,直接使用OLOO-style,间接使用constructor-style.

"按原样,ObjB必须从ObjA创建.. Object.create(ObjB)等"

不,ObjB这不是任何类的实例(在基于经典的语言中) ObjA.可以说像objB对象ObjA在创建时被委托给对象".如果你使用构造函数,你会做同样的'耦合',尽管间接地通过使用.prototypes.