Choosing an OOP pattern in javascript

2019-02-03 14:35发布

I've put these together with the help of others and several resources. I've made a fiddle of everything, and the stripped down code is posted below.

Basically I've learned how to use each of these patterns but I'm curious about the more fundamental differences between these approaches. Downstream code is practically identical with any of these patterns, but is there a reason why one should use one over another, beyond personal preference? Also though I've tried to gather up the most common patterns, please suggest your own if it's better.

Pattern 1 (Object Based):

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

mouseDiff.init();

Pattern 2 (Most traditional as far as I know):

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

Pattern 3 (Making use of closure):

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

6条回答
别忘想泡老子
2楼-- · 2019-02-03 15:13

http://www.jsoops.net/ is quite good for oop in Js. If provide private, protected, public variable and function, and also Inheritance feature. Example Code:

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
查看更多
ゆ 、 Hurt°
3楼-- · 2019-02-03 15:16

This has been answered elsewhere many times before but just to offer up some variety. ds.oop is a nice way to declare classes with constructors in javascript. It supports every possible type of inheritance (Including 1 type that even c# does not support) as well as Interfaces which is nice.

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
查看更多
孤傲高冷的网名
4楼-- · 2019-02-03 15:18

I do not know enough about JavaScript to tell you what if any performance differences exist between these approaches. Here are just two differences between these I noticed. I'm sure there are others.

Pattern 1 creates one object with those properties, including attached methods. Pattern 2 allows us to easily create many objects with the same methods attached, without rewriting them.

Pattern 3 is like a factory. Instead of relying on prototype to automatically attach these methods, the factory just creates them anew and returns the object. The usage of a closure allows us to hide the "member variables" of the object. There is no way to access startPoint or hypotenuse() except through the "public" interface returned.

Whenever I answer these types of theoretical JavaScript questions, I always fear there is some technical detail I am forgetting or overlooking. If so, let me know, and I will fix the answer.

查看更多
冷血范
5楼-- · 2019-02-03 15:21

Pattern two is my personally preference because it is the closest to traditional object oriented programming and allows for easy inheritance.

This post by John Resig (the guy behind JQuery) uses that method... http://ejohn.org/blog/simple-javascript-inheritance/

[edit]

In fact, I don't think methods 1 or 3 have any benefits. 1 is, as someone else said, a singleton and does not allow multiple instances and 3... I don't know where to start with 3.

查看更多
Bombasti
6楼-- · 2019-02-03 15:26

Pattern 1 is a singleton. If you only need one such object, it's just fine.

Pattern 2 builds new objects, and takes advantage of the prototype object so that when a new MouseDiff object is created, it will not create new copies of the functions (which are themselves data in JavaScript).

Pattern 3 requires more memory in comparison to a regular singleton but it offers static privacy.

I like the following pattern, as it covers various features, though it is really a combination of the constructor (pattern 2) and closure (pattern 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();
查看更多
我只想做你的唯一
7楼-- · 2019-02-03 15:28

There is one more possible way to do this.

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

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

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

Here we simply pass the namespace as an argument to a self-invoking function. The privateVarialble variable is private because it does not get assigned to the context.

We can even set the context to the global object (with a one word change!). Inside brackets (MouseDiff) make it (this). This is a big asset for library vendors – who can wrap their features in a self-invoking function and leave it to the user to decide whether they should be global or not.

查看更多
登录 后发表回答