首页 文章

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

提问于
浏览
98

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

这是他的书"You Don't Know JS: this & Object Prototypes"中的an example of Kyle's pattern

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."

8 回答

  • -1

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

    OLOO按原样包含原型链,无需在其他(IMO混淆)语义上进行分层以获得链接 .

    因此,这两个片段具有完全相同的结果,但以不同方式实现 .

    Constructor Form:

    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 Form:

    var FooObj = { y: 11 };
    
    var BarObj = Object.create(FooObj);
    BarObj.z = 31;
    
    var x = Object.create(BarObj);
    x.y + x.z;  // 42
    

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

    片段之间的关系和委托是相同的 . 片段之间的内存使用情况相同 . 在片段之间创建许多"children"(也就是诸如 x1x1000 之类的许多对象)的能力是相同的 . 代理的性能( x.yx.z )在代码段之间是相同的 . OLOO的对象创建性能较慢,但sanity checking that表明性能较慢并不是问题 .

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

    OLOO is just cutting out the middle-man.

    这是 class 与OLOO的another comparison .

  • 24

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

    优点:

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

    1.简洁

    OLOO依赖 Object.create() 创建一个新对象,该对象与另一个对象链接 . 您不必了解函数具有 prototype 属性或担心其修改带来的任何潜在相关缺陷 .

    2.更清晰的语法

    这是有争议的,但我觉得OLOO语法(在很多情况下)比'standard'javascript方法更整洁,更简洁,特别是在涉及多态( super -样式调用)时 .

    缺点:

    我认为有一个有问题的设计(实际上有助于上面的第2点),这与阴影有关:

    在行为委托中,我们避免在[[Prototype]]链的不同级别上尽可能地命名相同的东西 .

    这背后的想法是对象有自己更具体的功能,然后内部委托给链下游的功能 . 例如,您可能有一个带有 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会好得多,但它只是一个例子 . )

  • 2

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

    1

    当一个经典模式中的另一个"inherits"时,这两个函数可以声明类似的语法("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

    在经典模式中,我可以通过将它们直接分配给"class"函数(而不是 .prototype )来创建"static"实用函数,这些函数不直接应用于"instant" . 例如 . 像下面代码中的函数 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));}
    };
    
  • 138

    “我想这样做会使每个obj依赖于另一个”

    正如Kyle解释当两个对象被链接时,它们不能通过 [[Prototype]] 链接将一个对象链接到另一个对象,您可以随时更改它 . If you take two [[Prototype]] linked objects created through OLOO style as being dependent on each other, you should also think the same about the ones created through constructor calls.

    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后者与前一代码的唯一区别在于,后者通过 constructor 函数( Foo.prototypeBar.prototypeBaz.prototype )的任意对象将 foobarbaz 对象相互链接,但在前者中( OLOO style)它们是直接链接的 . 这两种方式只是将 foobarbaz 彼此直接连接,直接连接到前者,间接链接到后者 . 但是,在这两种情况下,对象都是彼此独立的,因为它不能从其他类继承 . 您始终可以更改对象应该委派的对象 .

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

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

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

    是的,这确实可能 -

    让我们使用 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
    

    你认为 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 -style中,一个对象委托给 Tech (委托是显式设置的),而在 constructor -style中,一个对象委托给 Tech.prototype (委托是隐式设置的) . 最终,您最终将三个对象(彼此之间没有链接)链接到一个对象,直接使用 OLOO -style,间接使用 constructor -style .

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

    不, ObjB 这里不是任何类 ObjA 的实例(在经典语言中) . It sould be said like objB object is made delegate to ObjA object at it's creation time" . 如果您使用了构造函数,那么您可以使用 .prototype 进行相同的'coupling' .

  • 13

    @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中的静态方法 . 我'm not sure if I like it or not. It requires calling a special property at the top of any 1292186 methods. For example, I'方法使 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);
                ...
    

  • -1

    @james emanon - 所以,你指的是多重继承(在“你不了解JS:这个和对象原型”一书中的第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 Prototypes”):

    换句话说,实际的机制,即我们可以在JavaScript中利用的功能的重要性,是关于与其他对象链接的对象 .

    我知道更自然的方式是在一个地方/函数调用中声明所有“父”(仔细:))对象,而不是建模整个链 .

    它需要的是根据这一点在我们的应用程序中改变思维和建模问题 . 我也习惯了 . 希望它有所帮助,凯尔本人的最终判决将是伟大的 . :)

  • 3

    @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() 函数 . ;-)

  • 5

    有没有办法让OLOO超过“两个”对象..所有的例子我都是基于基础的例子(参见OP的例子) . 假设我们有以下对象,我们如何创建一个具有“其他”三个属性的“第四”对象?翼...

    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中实现这一目标的模式是什么?

相关问题