【问题标题】:Choosing an OOP pattern in javascript在 javascript 中选择 OOP 模式
【发布时间】:2011-06-14 19:43:04
【问题描述】:

我在其他人和一些资源的帮助下将这些放在一起。我创建了一个fiddle of everything,精简后的代码贴在下面。

基本上我已经学会了如何使用这些模式,但我对这些方法之间更根本的区别感到好奇。下游代码实际上与这些模式中的任何一种都相同,但是除了个人喜好之外,是否有理由使用一种而不是另一种呢?此外,虽然我尝试收集最常见的模式,但如果更好,请提出您自己的模式。

模式 1(基于对象):

var mouseDiff = {
    "startPoint" : {"x" :0, "y" : 0},
    "hypotenuse" : function(a,b) {
        // do something
    },
    "init"       : function(){
        // do something
    }
}

mouseDiff.init();

模式 2(据我所知最传统):

function MouseDiff() {
    this.startPoint = {"x" :0, "y" : 0};
}

MouseDiff.prototype.hypotenuse = function(a,b) {
    // do something
}

MouseDiff.prototype.init = function() {
    // do something
}

var myMouse = new MouseDiff;
myMouse.init();

模式 3(使用闭包):

var MouseDiff2 = (function() {
    var startPoint = {"x" :0, "y" : 0};
    var hypotenuse = function(a,b) {
        // do something
    };
    return {
        hypotenuse: hypotenuse,
        init : function(){
            // do something
        }
    };

}());
MouseDiff2.init();

【问题讨论】:

  • Pattern 4 : Object.create。因为这就是你做原型 OO 的方式。
  • 为了详细说明@Raynos 的评论,Object.create 最常与模式1 结合使用。调用var mouseDiff2 = Object.create(mouseDiff) 会为您提供一个新的、单独的对象,其引用存储在mouseDiff2 中。然后,您可以随心所欲地使用它,而不会影响原始对象。
  • @RyanKinal 这并不完全正确。您可以编辑原始对象。还有一点需要注意的是mouseDiff是放在mouseDiff2的原型链中
  • @Raynos 哦,哇。是的,那是我的错。今天头有点不清楚。 Object.create 通过原型链为您提供一个继承旧对象的新对象。
  • 澄清一下。 var o1 = { "foo": { "bar": "baz" } }; var o2 = Object.create(o1); o2.foo.bar = 42; console.log(o1.foo.bar); 是原始“克隆”对象破坏状态的示例。

标签: javascript oop model-view-controller design-patterns


【解决方案1】:

模式 1 是单例。如果你只需要一个这样的对象,那很好。

模式 2 构建新对象,并利用 prototype 对象,因此当创建新的 MouseDiff 对象时,它不会创建函数的新副本(它们本身就是 JavaScript 中的数据)。

与常规单例相比,模式 3 需要更多内存,但它提供静态隐私。

我喜欢下面的模式,因为它涵盖了各种特性,虽然它实际上是构造函数(模式 2)和闭包(模式 3)的组合:

var MouseDiff = (function () {

    var aStaticVariable = 'Woohoo!';
    // And if you really need 100% truly private instance
    // variables which are not methods and which can be
    // shared between methods (and don't mind the rather
    // big hassle they require), see
    // http://brettz9.blogspot.com/search?q=relator
    // (see also the new plans for a Map/WeakMap in ECMAScript)

    function _APrivateStaticMethod () {
        alert(aStaticVariable);
    }

    // An instance method meant to be called on the
    //   particular object as via ".call(this)" below
    function _APrivateInstanceMethod () {
        alert(this.startPoint.x);
    }

    // Begin Constructor
    function MouseDiff() {
        this.startPoint = {"x" :0, "y" : 0};
    }

    MouseDiff.prototype.hypotenuse = function(a,b) {
        // do something
    };

    MouseDiff.prototype.init = function() {
        // do something
        _APrivateStaticMethod(); // alerts 'Woohoo!'
        _APrivateInstanceMethod.call(this); // alerts 0 (but if not
        // called with this, _APrivateInstanceMethod's internal
        // "this" will refer (potentially dangerously) to the
        // global object, as in the window in the browser unless
        // this class was defined within 'strict' mode in which
        // case "this" would be undefined)
    };

    return MouseDiff;
}());

var myMouse = new MouseDiff;
myMouse.init();

【讨论】:

  • Pedantry 指出 singleton 在 JavaScript 这样的语言中是一个愚蠢的术语,因为这个概念与 JS 中不存在的类结合在一起。
  • 当然。但实际上,由于我们是通过将事物与我们所知道的联系起来来学习的,所以我认为这样描述它仍然是有帮助的,并认为 new 关键字使函数可以用作“类”。
  • 单身仍然是错误的词。这是一种只有一个对象与特定接口匹配的方法(由 C#/Java 中的类定义)。你不能在 JS 中这样做,因为你可以创建另一个打破单例概念的方法。我很欣赏使用常用术语很有帮助,但在这种情况下 Singleton 被滥用了(不用担心,每个人都会这样做。)
  • 实际的区别是什么?人们可能会这样想:您使用的每个对象字面量都在生成一个匿名类,并且它遵循自己的接口。所以你以后可以创建的“其他对象”在概念上只是创建一个不同的类和实例。
  • 我认为在概念界面中是一种简单的打字方式。如果它有.quack.swim,那么它就是Duck。所以两者都实现了相同的概念接口(你的文档)。 Interface 对象的实例可能存在这一事实无关紧要。就像我说的那样,这是迂腐。
【解决方案2】:

我对 JavaScript 的了解还不够,无法告诉您这些方法之间是否存在任何性能差异。这只是我注意到的两个区别。我确定还有其他人。

模式 1 创建一个具有这些属性的对象,包括附加的方法。模式 2 允许我们轻松创建许多附加相同方法的对象,而无需重写它们。

模式 3 就像一个工厂。工厂不再依赖原型自动附加这些方法,而是重新创建它们并返回对象。闭包的使用允许我们隐藏对象的“成员变量”。没有办法访问startPointhypotenuse(),除非通过返回的“公共”接口。

每当我回答这些类型的理论 JavaScript 问题时,我总是担心我会忘记或忽略一些技术细节。如果是这样,请告诉我,我会修复答案。

【讨论】:

  • 模式 3 可能是一个工厂,但由于它立即调用,所以它不是。
【解决方案3】:

模式二是我个人的偏好,因为它最接近传统的面向对象编程,并且易于继承。

John Resig(JQuery 背后的人)的这篇文章使用了这种方法...... http://ejohn.org/blog/simple-javascript-inheritance/

[编辑]

事实上,我认为方法 1 或 3 没有任何好处。正如其他人所说,1 是一个单例,不允许多个实例和 3...我不知道从哪里开始 3。

【讨论】:

  • 问题是两者都被立即调用来创建一个对象,而不是以它们的通用形式呈现。方法 1 和 3 在其通用工厂格式中实际上比方法 2 具有 有效 优势。
  • 模式 1 的好处是方便,模式 3 的好处是静态的(并且有很多额外的工作)实例隐私。
  • 我刚刚注意到关于#3。私有变量的东西很有趣。我仍然不认为失去使用 instanceof 和继承的能力。 #1 和 #3 更像是命名空间而不是对象。
【解决方案4】:

还有另一种可能的方法来做到这一点。

var MouseDiff = {};
(function(context) { 
    var privateVarialble = 0;

    context.hypotenuse = function() {
         //code here    
    };

    context.int = function() {
      //code here    
    }
})(MouseDiff); 

这里我们只是将命名空间作为参数传递给自调用函数。 privateVarialble 变量是私有的,因为它没有被分配给上下文。

我们甚至可以将上下文设置为全局对象(只需一个字更改!)。括号内(MouseDiff) 使其成为(this)。这对于库供应商来说是一笔巨大的财富——他们可以将他们的功能包装在一个自调用函数中,并让用户决定它们是否应该是全局的。

【讨论】:

    【解决方案5】:

    http://www.jsoops.net/ 非常适合 Js 中的 oop。如果提供私有、受保护、公共变量和函数,以及继承特性。示例代码:

    var ClassA = JsOops(function (pri, pro, pub)
    {// pri = private, pro = protected, pub = public
    
        pri.className = "I am A ";
    
        this.init = function (var1)// constructor
        {
            pri.className += var1;
        }
    
        pub.getData = function ()
        {
            return "ClassA(Top=" + pro.getClassName() + ", This=" + pri.getClassName()
            + ", ID=" + pro.getClassId() + ")";
        }
    
        pri.getClassName = function () { return pri.className; }
        pro.getClassName = function () { return pri.className; }
        pro.getClassId = function () { return 1; }
    });
    
    var newA = new ClassA("Class");
    
    //***Access public function
    console.log(typeof (newA.getData));
    // function
    console.log(newA.getData());
    // ClassA(Top=I am A Class, This=I am A Class, ID=1)
    
    //***You can not access constructor, private and protected function
    console.log(typeof (newA.init));            // undefined
    console.log(typeof (newA.className));       // undefined
    console.log(typeof (newA.pro));             // undefined
    console.log(typeof (newA.getClassName));    // undefined
    

    【讨论】:

      【解决方案6】:

      这已经在其他地方多次回答过,但只是为了提供一些多样性。 ds.oop 是在 javascript 中使用构造函数声明类的好方法。它支持所有可能的继承类型(包括一种甚至c#都不支持的类型)以及很好的接口。

      var Color = ds.make.class({
          type: 'Color',
          constructor: function (r,g,b) { 
              this.r = r;                     /* now r,g, and b are available to   */
              this.g = g;                     /* other methods in the Color class  */
              this.b = b;                     
          }
      });
      var red = new Color(255,0,0);   // using the new keyword to instantiate the class
      

      【讨论】:

        猜你喜欢
        • 2012-11-18
        • 1970-01-01
        • 2023-04-10
        • 2013-04-28
        • 2015-05-01
        • 2012-03-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多