views:

889

answers:

9

I am trying to wrap my head around the idea of classes, data visibility and closures (specifically in Javascript) and I am On the jQuery docs page for types, it mentions that closures are used to hide data:

The pattern allows you to create objects with methods that operate on data that isn't visible to the outside—the very basis of object-oriented programming.

The example:

function create() {
  var counter = 0;
  return {
    increment: function() {
      counter++;
    },
    print: function() {
      console.log(counter);
    }
  }
}
var c = create();
c.increment();
c.print(); // 1

By declaring the variable counter with the keyword var, it is already locally scoped inside the function/class definition. As far as I know and can tell, it isn't accessible from the outside to begin with. Am I missing something from a data visibility perspective.

Second, is there an advantage to writing the class like above versus like below:

function create() {
  var counter = 0;
  this.increment = function() {
      counter++;
  }
  this.print = function() {
      console.log(counter);
  }
  return this;
}
var c = create();
c.increment();
c.print(); // 1

As I understand it, these are more or less semantically the same thing - the first is just more "jQuery style". I am just wondering if there is an advantage or other nuance I don't fully appreciate from the first example. If I am correct, both examples create closures in that they are accessing data declared outside their own scope.

http://docs.jquery.com/Types#Closures

+1  A: 
Joel Coehoorn
I'm pretty sure the act of using the variable in the anonymous function means that it will persist, even though the function has exited and normally that variable would be discarded.
Dave
+6  A: 

First of all, you are correct that both versions use closures.

The first version is cleaner (in my opinion) and more popular in modern javascript. The major potential drawback of the first style is that you cannot effectively assign objects to the constructor's prototype, which is useful (and more efficient) if you are creating a lot of the same objects.

The second style, I've actually never seen in production Javascript. Normally, you would instantiate create with new, instead of returning this in the create() function, like so:

function create() {
  var counter = 0;
  this.increment = function() {
      counter++;
  }
  this.print = function() {
      console.log(counter);
  }
}
var c = new create();
c.increment();
c.print(); // 1
Triptych
And personally, this is the way I prefer to create objects, after having tried innumerable techniques. I think this is just so much cleaner and easier to understand. But that's me. :)
Jason Bunting
Minor nit: returning "this" is equivalent to not explicitly returning anything at all, when using the new operator. Without the new operator, "this" is the current context (often times global), and such you end up clobbering global variables.
ken
+2  A: 

Well, I don't care to get into a religious war over how to create objects in JavaScript, since some people feel strongly that there is a right and wrong way to do it.

However, I want to point out something in your second set of code that isn't too savory - namely, the fact that you are assigning things new properties on the object contained in the this keyword - do you realize what that object is? It isn't an empty object unless you use instantiation syntax like this:

var c = new create();

When you do that, the this keyword inside the body of the constructor function is assigned a brand new object, as though the first line in the body were something like:

this = {};

But when you call create() as a function, as you do in that example, you are altering the scope outside of the function's definition (as alluded-to by @seanmonster in the comments).

Jason Bunting
Unless assigning the function to a prototype or other object, "this" is actually the global object (window).
seanmonstar
Hee hee, my bad - your comment's grammar is so subtle and I took it to say something opposite of what it said. Sorry about that. I gotta get off of these weird meds...
Jason Bunting
I appreciate your answer - although typically true, to be more precise, as I have reflected in my now-edited answer, the this keyword could be something other than the global object, depending on where the function definition is found. If his "create" function were nested inside a constructor function, which is entirely valid, the this keyword would be different than the global scope. So, again - I appreciate your bringing this up because it can be tricky business...
Jason Bunting
its even trickier. if you do have a function inside a constructor, it's not necessarily bound to the new object. example: var Toy = function() { this.type = 'ball'; var create = function() { this.prop = 'i just accessed window'; } create(); } Any time you set up var something = function() { }, "this" is the global object by default. It's only different if it's a property of an object (var obj = { create: function() { //this == obj} }), or Toy.prototype.create = function() { //this == new Toy }). Can be scary if not paying attention.
seanmonstar
+1  A: 

Christian Heilmann has a fairly decent article on the module pattern that you describe that might help you wrap your head around it and why it's useful.

Steerpike
+2  A: 

You should compare the example against this snippet

function create() {
  this.counter = 0;
  this.increment = function() {
      this.counter++;
  };
  this.print = function() {
      console.log(counter);
  }
}

var c = new create();
c.increment();
c.print(); // 1

So when new create() is called it initializes the new object with two methods and one instance variable (namely: counter). Javascript does not have encapsulation per-se so you could access c.counter, as follows:

var c = new create();
c.increment();
c.counter = 0;
c.print(); // 0

By using closures (as shown in your examples) counter is now longer an instance field but rather a local variable. On the one hand, you cannot access from outside the create() function. On the other hand, increment() and print() can access because they close over the enclosing scope. So we end up with a pretty good emulation of object-wise encapsulation.

Itay
+1  A: 

By declaring the variable counter with the keyword var, it is already locally scoped inside the function/class definition. As far as I know and can tell, it isn't accessible from the outside to begin with. Am I missing something from a data visibility perspective.

It's not that the counter variable isn't accessible from outside the function to begin with, it's that it is accessible to the increment and print functions after create function has exited that makes closures so useful.

Russ Cam
+1 for answering the part of the question everybody else is avoiding for some reason
George Jempty
A: 

In your second example, when you call create(), within the scope of the function, this is the global object (which is always the case when you call a "bare" function, without using it as a constructor or accessing it as a property (e.g. a "method" call)). In browsers, the global object is window. So when you call create subsequent times, it creates new closures, but you then assign them to the same global object as before, overwriting the old functions, which is not what you want:

var c = create(); // c === window
c.increment();
c.print(); // 1
var c2 = create(); // c2 === c === window
c.print(); // 0
c2.print(); // 0
increment(); // called on the global object
c.print(); // 1 
c2.print(); // 1

The solutions, as others have pointed out, is to use new create().

Miles
+1  A: 

This syntax makes more sense to me coming from an OOP background:

Create = function {
  // Constructor info.

  // Instance variables
  this.count = 10;
}

Create.prototype = {

     // Class Methods
     sayHello : function() {
          return "Hello!";
     },

     incrementAndPrint : function() {
          this.count++;

          // Inner method call.
          this.print();
     },

     print : function() {
         return this.count;
     }


}

var c = new Create();
alert(c.incrementAndPrint());
Matt