tags:

views:

171

answers:

6

There seem to be many different ways of doing OO in JavaScript.

I like:

function ClassA(){};
ClassA.prototype={
    someFunc:function(a,b,c){},
    otherFunc:function(){}
}
var c=new ClassA();

and have never used features beyond what this provides (despite being a proficient OOer). I suspect this is old fashioned, because every so often I see new spangled variants, and it makes me wonder if I'm choosing the best approach. For instance, you can do magic in the constructor method to create private variables and accessor methods, something I believed (until relatively recently) to be impossible. What about subclassing? I wouldn't know how to achieve this, but it must have some sort of common pattern by now.

How do you do it and why?

+1  A: 

I think joose is a pretty cool way to do OOP in javascript
http://code.google.com/p/joose-js/

dave
+4  A: 

Simple JavaScript Inheritance

Because John Resig said so.

Anton
Yeah, but he also popularised $ as a variable name... unforgivable.
spender
not when $ is pretty much treated as a language extension
Matt Briggs
+4  A: 
function foo() {
  var bar = function() { console.log("i'm a private method"); return 1; };
  var iAmAPrivateVariable = 1;

  return {
    publicMethod: function() { alert(iAmAPrivateVariable); },
    publicVariable: bar()
  }
}

//usage
var thing = foo()

This is known as a functional appoach, since you are actually leveraging closures for encapsulation (which is the only way to do it in javascript).

In a general way, you shouldn't be doing OO in javascript, it isn't that great a language for it for a great many reasons. Think scheme with squiggly brackets and semi-colons, and you will start writing the language like the pros do. That being said, sometime OO is a better fit. In those cases, the above is typically the best bet

EDIT: to bring inheritance into the mix

function parent() {
  return { parentVariable: 2 };
}

function foo() {
  var bar = function() { console.log("i'm a private method"); return 1; };
  var iAmAPrivateVariable = 1;

  me = parent();
  me.publicMethod = function() { alert(iAmAPrivateVariable); };
  me.publicVariable = bar();

  return me;
}

This makes things a tad more complected, but accomplishes the desired end result while still taking a functional approach to OO concepts (in this case, using decorator functions instead of real inheritance). What I like about the whole approach is we are still really treating objects the way they are intended to be in this kind of language -- a property bag you can attach stuff to at will.

EDIT2:

Just wanted to give credit where credit is due, this approach is a very slight simplification to what doug crockford suggests in Javascript: The Good Parts. If you want to take your js skills to the next level, I would highly suggest starting there. I don't think I have ever learned so much from such a small book.

Another note is this is wildly different then what you will see most of the time in most of the jobs you will ever work at, and often is very hard to explain a) what is going on, and b) why it is a good idea to coworkers.

Matt Briggs
I like this alot. Can it be bent to provide inheritance without the use of prototype?
spender
`bar` will become a property of the global object (`window.bar`), because it isn't declared with the `var` statement in the scope of the `foo` function. Also under ECMAScript 5 Strict Mode, that will give you a `ReferenceError`, so always use `var` to *declare* a variable ;)
CMS
@CMS wow... that was embarassing...
Matt Briggs
The only problem with this method is that it returns an anonymous object that can no longer be identified as an instance of `foo`.
casablanca
@spender: edited to add inheritance
Matt Briggs
@casablanca: that is totally true, but it is also not that important 99.9% of the time in dynamic languages. you care that an object can do something, not that it is something.
Matt Briggs
It was Crockford's article about using closure for private variables that inspired me to ask this question. I'm deeply suspicious of libraries created that try to emulate class based OO too closely, because I don't think it's what JS is about, otherwise there'd be language support by now. I really like applying functional concepts to Javascript. I think I'll take your book recommendation. Ta!
spender
I'm completely unworried about those who don't/won't get it... I won't be employing those sorts at my company!
spender
Glad to have helped :)
Matt Briggs
+1  A: 

"Subclassing" in JavaScript generally refers to prototype-based inheritance, which basically follows this pattern:

function Superclass() { }
Superclass.prototype.someFunc = function() { };

function Subclass() { }
Subclass.prototype = new Superclass();
Subclass.prototype.anotherFunc = function() { };

var obj = new Subclass();

This builds a "prototype chain" from obj -> Subclass.prototype -> Superclass.prototype -> Object.prototype.

Pretty much every OOP library for JavaScript builds upon this technique, providing functions that abstract most of the prototype "magic".

casablanca
this isn't a good idea; it relies on calling the parent constructor to get the prototype for the child constructor. Better to do something like this (see my response): `function C(){}; function clone(obj){C.prototype=obj, return new C}; Subclass.prototype=clone(Superclass.prototype);` ... that way the parent ctor isn't called, which could create unwanted side effects.
no
+1  A: 

Objects in JavaScript are unlike almost all the other high-profile languages. Instead of being class-based (like in Java, C++, PHP, etc etc), they are prototype-based. As such, the basic paradigm of object-oriented programming has to be considerably modified. People who can't or don't want to re-think this and insist on using class-based thinking have to build class-based logic in JavaScript or use code from someone else who has already built it.

staticsan
completely agree. faking classes in JavaScript is trying to shoe horn java into something that only bares a superficial resemblance to it.
Matt Briggs
A: 

I like to do something like

// namespace "My"
var My = new function {

  // private methods
  /**
   * Create a unique empty function.
   * @return {Function} function(){}
   */
  function createFn () {return function(){}}

  /** A reusable empty function. */
  function emptyFn () {}

  /**
   * Clone an object
   * @param {Object}  obj     Object to clone
   * @return {Object}         Cloned object
   */
  function clone (obj) { emptyFn.prototype=obj; return new emptyFn() }

  // public methods
  /**
   * Merge two objects
   * @param {Object} dst        Destination object
   * @param {Object} src        Source object
   * @param {Object} [options]  Optional settings
   * @return {Object}           Destination object
   */
  this.merge = function (dst, src, options) {
    if (!options) options={};
    for (var p in src) if (src.hasOwnProperty(p)) {
      var isDef=dst.hasOwnProperty(p);
      if ((options.noPrivate && p.charAt(0)=='_') || 
          (options.soft && isDef) || 
          (options.update && !isDef)) continue;
      dst[p]=src[p]; 
    }
    return dst;
  }

  /**
   * Extend a constructor with a subtype
   * @param {Function} superCtor      Constructor of supertype
   * @param {Function} subCtor        Constructor of subtype
   * @param {Object} [options]        Optional settings
   * @return {Function}               Constructor of subtype
   */
  this.extend = function (superCtor, subCtor, options) {
    if (!subCtor) subCtor=createFn();
    if (!options) options={};
    if (!options.noStatic) this.merge(subCtor, superCtor, options); 
    var oldProto=subCtor.prototype;
    subCtor.prototype=clone(superCtor.prototype);
    this.merge(subCtor.prototype, oldProto);
    if (!options.noCtor) subCtor.prototype.constructor=subCtor; 
    return subCtor;
  }

}

And then something like...

// namespace "My.CoolApp"
My.CoolApp = new function(){

  // My.CoolApp.ClassA
  this.ClassA = new function(){

    // ClassA private static
    var count=0; 

    // ClassA constructor 
    function ClassA (arg1) {
      count++;
      this.someParam=arg1;
    }

    // ClassA public static
    My.merge(ClassA, { 
      create: function (arg1) {
        return new ClassA(arg1);
      }
    }

    // ClassA public
    My.merge(ClassA.prototype, {
      doStuff : function (arg1) {
        alert('Doing stuff with ' + arg1);
      },
      doOtherStuff : function (arg1) {
        alert('Doing other stuff with ' + arg1);
      }
    }

    return ClassA;
  }

  // My.CoolApp.ClassB
  this.ClassB = new function(){

    My.extend(My.CoolApp.ClassA, ClassB);
    // ClassB constructor
    function ClassB () {
      ClassA.apply(this, arguments);
    }

    return ClassB;
  }

}

...the clone function is the key to inheritance. In short:

  • Clone an object by making it the prototype of a throwaway function and calling the function with 'new'.
  • Clone the parent constructor's prototype, and set it the result as the prototype of the child class.
no