2

There are lots of ways of doing the same thing in JavaScript. I have however picked up some ways, and some ways I frankly don't understand. Could anyone please help me clarify some things? (I first learnt OOP in PHP.)

So a class can be made like this:

var object = new class(constructparams) {
    var private_members; // Can be accessed from within the code inside this definition only.
    this.public_members; // Can be accessed from everywhere.

    var private_functions = function() {}
    this.public_functions = function() {}
}

object.prototype.semi_public_members = function() {
    // Will be public, but can only access public members and methods.
    // E. g. private_members; is not available here.
}

Is this all correct so far?

Then someone likes the self-executing anonymous function approach to create a namespace. What is the point of that, when you have this way above that does the same thing, provides a namespace?

And lastly you have the object literal notation that I don't understand.

var object = { // Something strange in here }

What is going on in there? Is it JSON? How is it used, how can I use it. What are the benefits of using this way instead of using the method I described? Why would you prototype instead of making the class correctly the first time?

Student of Hogwarts
  • 1,108
  • 3
  • 14
  • 28
  • 1
    Technically speaking, JavaScript doesn't have classes. However, folks often use objects as if they were. Your last example is just assignment of an object literal. – Brad Nov 03 '12 at 22:10

4 Answers4

5

Explaining the behaviour of different things in a constructed object by example:

// Defined as a variable from an anonymous function
// so that there is scope closure over variables
// shared across all instances and the prototype.
// If this isn't important, you don't need to close
// scope around it, so define directly
var ConstructedObject = (function constructorCreator () {
    // Define any variables/methods to be shared across
    // all instances but not polluting the namespace
    var sharedVariable = 'foo';

    // Next the actual constructor
    function ConstructedObject () {
        // Variables here are normally used to help
        // each instance and will be kept in memory as
        // long as the instance exists
        var instanceVariable = 'bar';
        // instance-specific properties get defined
        // using the "this" keyword, these are the
        // properties expected to be changed across
        // each different instance
        this.instanceProperty = true;
        this.instanceMethod = function () { return instanceVariable; };
        this.changeInstanceVar = function () { instanceVariable = 'foo'; };
            // you do have access to the shared
            // variables here if you need them.
    }
    // After the constructor, you set up the
    // prototype, if any. This is an object of shared
    // properties and methods to be inherited by every
    // instance made by the constructor, and it also
    // inherits the prototype's prototype, too.
    // Lets use a literal object for simplicity.
    ConstructedObject.prototype = {
        // Accessing the instance to which a method
        // applies is done using the "this" keyword,
        // similar to in the constructor
        sharedMethod : function () { return [sharedVariable, this.instanceMethod(),this.instanceProperty]; },
        changeSharedVar : function () { sharedVariable = 'bar'; }
        // properties may also be defined
    };
    // Finally, the constructor is returned so it
    // can be kept alive outside of the anonymous
    // function used to create it
    return ConstructedObject;
// and the anonymous function is called to execute
// what we've done so far
})();

After executing the above code, you have a constructor that creates objects with both instance-specific and shared variables. Now let's look at how they behave by creating two of instances and comparing them before and after some changes.

// First create the two instances
var myObjA = new ConstructedObject(),
    myObjB = new ConstructedObject();
// Now compare them, the sharedMethod method we
// used in the prototype offers an easy way to
// do this
console.log( myObjA.sharedMethod(), myObjB.sharedMethod() );
// ["foo", "bar", true] ["foo", "bar", true]
// Next lets change the different variables in
// myObjB so we can see what happens, again the
// change* methods defined before let us do this
// easily
myObjB.changeInstanceVar();
myObjB.changeSharedVar();
// For completeness, lets also change the property
// on myObjB.
myObjB.instanceProperty = false;
// Now when we compare them again, we see that our
// changes to the myObjB instance have only changed
// the shared variables of myObjA
console.log( myObjA.sharedMethod(), myObjB.sharedMethod() );
// ["bar", "bar", true] ["bar", "foo", false]

Here are the two logged statements together for easier viewing

//     myObjA               myObjB
["foo", "bar", true] ["foo", "bar", true]
["bar", "bar", true] ["bar", "foo", false]
Paul S.
  • 64,864
  • 9
  • 122
  • 138
  • Thanks for your reply! This is a really solid answer. But may I ask why one would use this method as opposed to using the method I used in my example? As I see it, they both offer the same functionality. Is it that this takes up less memory? – Student of Hogwarts Nov 04 '12 at 08:42
  • I'm assuming you're talking about the whole thing being enclosed in an anonymous function? In the first comment I tried to explain this; it is to create closure over the shared variables by capturing them all in the same scope, without polluting `window` (or whatever scope you were in before). If you don't need shared variables in this way, you don't need to wrap the constructor/prototype in an anonymous function. – Paul S. Nov 04 '12 at 15:03
  • Where memory usage comes into the picture is either if there is a memory leak (not clearing out big variables when you're done with them but want your object to persist) or if you're making huge numbers of these objects where everything is on the instance - if it is shared, the instance only needs a reference, if it is on the instance, it needs to allocate new memory for it on creation. @StudentofHogwarts – Paul S. Nov 04 '12 at 15:17
  • Hmm, but what is the benefit of prototyping everything inside the same closure instead of just making methods with this.methodname? – Student of Hogwarts Nov 04 '12 at 20:26
  • Ahh! I think I've misunderstood something! – Student of Hogwarts Nov 04 '12 at 20:27
  • So `this` doesn't refer to the local object in an OOP sense. It is just pointing to the current object or function that it is inside, so what I did in my example is just defining a lot of methods inside the constructor object? – Student of Hogwarts Nov 04 '12 at 20:28
  • And so prototyping is defining separate methods that has really nothing to do with the constructor object. – Student of Hogwarts Nov 04 '12 at 20:29
  • But then, what is the benefit of prototyping instead of making `this.methods` inside the constructor? – Student of Hogwarts Nov 04 '12 at 20:30
  • `this` in JS usually means "the invoking object" or "the object it's a property of". When you prototype, all of the instances have the **same** prototype object; i.e. you can add new methods to all instances by adding them to the prototype (or change existing ones), even after they've been created. It also means you use less memory because there is only one copy of each method, whereas if they're defined in the constructor, there is a new copy for each instance. Furthermore (yes, even more!!) if, in the future, you want a new constructor to _extend_ the current, prototypes make this easy. – Paul S. Nov 05 '12 at 01:02
  • 2
    After endless searching on Google, I finally found a tutorial which explains the very basics of prototyping. I've just known _that_ it works, not _how_. This now makes perfect sense! Thanks again! – Student of Hogwarts Nov 20 '12 at 21:10
1

I think there are some concepts that seem to be missing but I'll try answering as much as I can.

So a class can be made like this...Is this all correct so far?

It's close but is not entirely correct. You don't need new to create a constructor function, you only need it when creating a new instance of the "class".

function Klass() { ... } // capitalized name as convention for constructors
var myKlass = new Klass(); //<-- Need "new" here

Public methods can be attached to the instance or to the prototype. When you attach it to the prototype the method will be shared across all instances, saving some memory.

Klass.prototype = {
  publicSharedMethod: function() {
    var self = this;
  }
}

Then someone likes the self-executing anonymous function... What is the point of that...

The module pattern (or self-execution function) approach is a bit different because you're typically not dealing with the prototype although you can. It's just a function that returns a literal object with properties and methods but I mainly use it for singletons when you don't need instances of that object:

var Singleton = (function() {
  var private = 'foo';
  var public = 'baz';
  var publicMethod = function() { ... }
  // Expose public methods and properties
  return {
    public: public
    publicMethod: publicMethod
  }
}());

And lastly you have the object literal notation that I don't understand.

That's just a regular object in JavaScript, similar to what PHP calls "associative array". Literal objects syntax are the base for JSON, but JSON has more limitations in terms of formatting; JavaScript is more forgiving so you can have unquoted properties and methods for example.

Why would you prototype instead of making the class correctly the first time?

The point here is to understand that JavaScript is not a traditional object oriented language, so there are no classes, and you shouldn't be thinking about classes. But prototypes are very powerful, I'd say even more powerful than classes. There are many examples online on how to replicate classes with prototypes but not the other way around.

elclanrs
  • 92,861
  • 21
  • 134
  • 171
  • Hmm, so you're basically saying that prototypes are static members? – Student of Hogwarts Nov 03 '12 at 22:24
  • And the Klass() is just an object that doesn't work because the constructor hasn't yet run on it to fill it with data? So if you have a Klass() with a method that alerts something, it would work without being instantiated? – Student of Hogwarts Nov 03 '12 at 22:25
  • I like the OOP ways of doing things because it doesn't pollute the global namespace, and it keeps things separated into modules that forces you to keep things organized and non-spaghetti. How would you outline the structure? Use my approach if you need multiple instances, and use prototypes if you need ONE instance? – Student of Hogwarts Nov 03 '12 at 22:30
  • Yes, I'd say they are similar to static members. `Klass` can be an function or a constructor function it all depends of how your call it. If you use `new` then it's a constructor otherwise it's a function, that's why it's recommended to _always_ capitalize your constructors to avoid this confusion. – elclanrs Nov 03 '12 at 22:31
  • If you want to run a method you need an instance. – elclanrs Nov 03 '12 at 22:32
  • So if you make something inside Klass() {} you need an instance. if you prototype Klass().hi = function {} you don't need an instance? – Student of Hogwarts Nov 03 '12 at 22:34
  • @Guffa said that "No, that is not correct. The constructor function should be separate from the creation of the object:" Does he mean that you should use prototype.foo instead of this.foo to save memory? – Student of Hogwarts Nov 03 '12 at 22:35
  • It is correct to declare the method inside the constructor but not efficient. If your function is meant to be a constructor then you _need_ to use `new` to call the method. If you use it as function the method will be undefined. Anyway there's no point on having a constructor acting as a function. – elclanrs Nov 03 '12 at 22:43
0

No, that is not correct. The constructor function should be separate from the creation of the object:

function myClass(constructparam1) {
  this.public_member = constructparam1; // Can be accessed from everywhere.
}

The methods should be in the prototype for the constructor, not the instance:

myClass.prototype.semi_public_members = function() {
  // Will be public
}

Call the constructor with the new keyword to create the instance:

var obj = new myClass(1337);

Local variables inside the constructor function can only be accessed inside that function. If you wan't local variables in your class, you need a function around it to create a closure, so that the methods in the prototype can access the variables:

var myClass = (function(){

  var local_variable;

  function constructor(constructparam1) {
    this.public_member = constructparam1; // Can be accessed from everywhere.
  }

  constructor.prototype.semi_public_members = function() {
    // Will be public
    alert(local_variable); // can access private variables
  }

  return constructor;
})();

Object literals is just a simple but limited way to create objects in one go. They don't have a prototype, so if you want them to have methods, you have to assign them to properties:

var obj = {

  public_member: 1337,

  semi_public_members: function(){
    alert(this.public_member);
  }

};
Guffa
  • 687,336
  • 108
  • 737
  • 1,005
  • So in your last example, the stuff inside the {} is just like doing prototype? In the example above that, are you "generating" an object called constructor inside the closure that you expose in the end so you can have ONE object and still access the private members? – Student of Hogwarts Nov 03 '12 at 22:33
  • @StudentofHogwarts: It's similar to using prototype, but the methods are members of the object itself instead of members of the prototype. If you create more than one of those, each instance will have its own copies of the function objects instead of sharing them. In the example above `constructor` is returned from the enclosing function so that the variable `myClass` contains the constructor function, and can be used just like in the first example. If you create more than one instance, they will share the local variables, so that's perhaps not the best example on how to do that. – Guffa Nov 03 '12 at 22:55
0
var object = new function () {
    //
}

var myObject = new object();
Mustafa Genç
  • 2,569
  • 19
  • 34