tags:

views:

303

answers:

5

Hi.

Just for the kicks i am trying to create a simple data object in javascript. Here is the code.

    var roverObject = function(){

        var newRover = {};
        var name;
        var xCord;
        var ycord;
        var direction;

        newRover.setName = function(newName) {
            name = newName;
        };

        newRover.getName = function() {
            return name;
        };

        newRover.setDirection = function(newDirection) {
            direction = newDirection;
        };

        newRover.getDirection = function() {
            return direction;
        };

        newRover.setXCord = function(newXCord) {
            xCord = newXCord;
        };

        newRover.getXCord = function() {
            return xCord;
        };

        newRover.setYCord = function(newYCord) {
            yCord = newYCord;
        };

        newRover.getYCord = function() {
            return yCord;
        };

        newRover.where = function(){
            return "Rover :: "+ name +" is at Location("+xCord+","+yCord+") pointing to "+direction;
        };

        return newRover;
    };


    rover1 = new roverObject();
    rover2 = new roverObject();     

    rover1.setName("Mars Rover");
    rover1.setDirection("NORTH");
    rover1.setXCord(2);
    rover1.setYCord(2);
    console.log(rover1.where());
    console.log(rover1);

    rover2.setName("Moon Rover");
    rover2.setDirection("SOUTH");       
    rover2.setXCord(1);
    rover2.setYCord(1);     
    console.log(rover2.where());        
    console.log(rover2); 

There are few questions that I have around this creation.

  1. I want to create an object where the properties/attributes of object are private and not visible to world. Am I successful in doing that? Can I really not access the object attributes?
  2. Is there a better way to create this kind of object?
  3. If I want to inherit this object, I should do a newObject.prototype = roverObjectwill that work? And will that make sense most of all.
  4. Finally I have a wierd problem. Notice the last method of objet "where" which returns a concatenated string. Here I tried following code instead.

            newRover.where = function(){
            return "Rover :: "+ name +" is at Location("+xCord+","+yCord+") pointing to "+direction;
        }();
    

and then did a following console.log

console.log(rover1.where);
console.log(rover2.where);

It threw following error for me:

cannot access optimized closure

Why would it say that? What am I doing wrong?

Thanks for all the help. Any review comments would be appreciated too! Cheers

+1  A: 

With regards to 4. - you are trying to log the function, not the result of calling the function. Should be console.log(rover1.where()); My guess firebug(I assume it's firebug's console.log) does not like to log function definitions.

EDIT Oh I get it, you are actually executing the where funcion when you assign rover.where. Are you trying to get what looks like a property to actually be a function? If that's the case it won't work. It will have to be a function if you want it to be evaluated when it's called.

What happens in you case where gets executed in the constructor function. At that point you are still creating the roverObject closure and hence it's too early to access it's private variables.

Igor Zevaka
that doesn't work. I am sure, if think if my closure function returns the value of function where, instead of function itself then where becomes an attribute right? Not a function.
Priyank
Correct - but when you call `console.log (rover1.where)`, you're trying to print out the function definition, which I presume Firebug cannot (in this case)
K Prime
See edit, i didn't realise the new syntax for `where` function :)
Igor Zevaka
really? Wouldn't function() { return "Rover :: " + name + " is at Location(" + xCord + "," + yCord + ") pointing to " + direction; }(); assign the function value and not the definition to where? And if it does assign the value, then why it cannot print it? Am confused.
Priyank
The syntax `function(){}()` will actually call that function (due to the brackets after the parenthesis). Try this: `var test = function(){alert("I am being called")}()`. You will see an alert pop up.
Igor Zevaka
I agree. I figured out. My ycord variable had a smaller "c" in cord and thats why firebug kept throwing error. If I make it yCord. Code works fine and shows a string appended with undefined, which is what I was expecting.Thanks for the help.
Priyank
+3  A: 

Am I successful in doing that? Can I really not access the object attributes?

Indeed. You don't have object attributes, you have local variables in the roverObject function. Local variables can't be accessed from outside, only from the functions inside the roverObject function that have a closure over them.

That you are calling roverObject as a constructor, with new roverObject, is irrelevant, as you are returning a different object from the function. Saying var rover1= roverObject() without the new would do exactly the same thing. Notably the object returned by [new] roverObject is a plain Object as you created it from {}; rover1 instanceof roverObject is false.

If you wanted instanceof to work, you would have to call with new, and use this instead of newRover in the constructor function.

If I want to inherit this object, I should do a newObject.prototype = roverObject will that work? And will that make sense most of all.

No. You currently have no allowance for prototyping. You are using a separate copy of each method for each instance of the roverObject. You can do certainly objects this way but it's a different approach than prototyping. If you wanted to make something like a subclass of roverObject in the arrangement you have now, you'd say something like:

function AdvancedRover() {
    var rover= new roverObject();
    rover.doResearch= function() {
        return rover.where()+' and is doing advanced research';
    };
    return rover;
}

Note since the ‘private’ local variables in the base class constructor really are private, even the subclass cannot get at them. There's no ‘protected’.

newRover.where = function(){ ... }();

What's that trying to do? I can't get the error you do; all the above does is assigns the string with the location to where (before the setter methods have been called, so it's full of undefineds).

Is there a better way to create this kind of object?

Maybe. see this question for a discussion of class/instance strategies in JavaScript.

bobince
excellent pointer! It provided tons of info. thanks
Priyank
A: 

This is just addressing point 1 of your post.

Here's a good article on javascript private members and more:
Private Members in JavaScript

o.k.w
+2  A: 

Q1: you can create 'private' members in javascript 'classes'. In javascript, privacy is not determined by any access specifier. Instead, access needs to be specifically instrumented. Example:

function MyClass() {
    this.val = 100; // public;
    var privateVal = 200;

    function getVal() { return this.val; } // private method;
    this.getPrivateVal = function() { // public method, accessor to private variable
        return privateVal;
    }
}

Object scope in javascript is governed by a queer concept called closures. AFAIK, there is no parallel concept in any other popular launguage like C+/Java etc.

While I understand what closures are, I cannot put it in words. Perhaps a demonstration will help you:

function closureDemo() {
    var done=false;
    function setDone() { done=true; }
    doLater(setDone);
}

function doLater(func) { setTimeout(func,1000); }

closureDemo();

now, while setDone is called from within doLater, it can still access done in closureDemo, even though done is not in scope (in the conventional procedural sense).

I think you will understand more when you read this.


Q2: I can only say what I do; I don't know if it is better or not. If I wrote your code, it would look like this:

function RoverObject() {

    var newRover = {}; // privates
    var name;
    var xCord;
    var ycord;
    var direction;

    this.setName = function(newName) {
        name = newName;
    };

    this.getName = function() {
        return name;
    };

    this.setDirection = function(newDirection) {
        direction = newDirection;
    };

    // and so on...

    this.where = function(){
        return "Rover :: "+ name +" is at Location("+xCord+","+yCord+") pointing to "+direction;
    };
}
var rover1 = new RoverObject();

Points to note:

  1. capitalization of "class name"'s first letter
  2. use of this instead of roverObject
  3. this function is a pure constructor. it returns nothing.

Q3: if you want to do inheritance, then my method (use of this) will not work. Instead, the public methods should be a part of the prototype of RoverObject. Read this. Excellent material.

Hope that helps.


EDIT: There is a problem with the way your code is doing work. Problems:

  1. your function does not do what its name suggests. Its name had better be createRoverObject, because that's exactly what it is doing. It is not working like a class constructor
  2. the methods supported by your class are part of the object, but the data members are not. While this may work (and it is not, as your console.log() problem suggests), it is not a good way to implement a class in javascript. The problem here is of closures. Again, i'm unable to articulate what the problem specifically is, but I can smell it.
Here Be Wolves
A: 

Defining your object like this gives you private members.

function RolloverObject() {
    var name;
    var xCord;
    var ycord;
    var direction;

    this.setName = function(newName) { name = newName; };
    this.getName = function() { return name; };

    this.setDirection = function(newDirection) { direction = newDirection; };
    this.getDirection = function() { return direction; };

    this.setXCord = function(newXCord) { xCord = newXCord; };
    this.getXCord = function() { return xCord; };

    this.setYCord = function(newYCord) { yCord = newYCord; };
    this.getYCord = function() { return yCord; };

    this.where = function() {
        return "Rover :: " + name + " is at Location(" + xCord + "," + yCord + ") pointing to " + direction; 
    };
}

var rolloverObject = new RolloverObject();
ChaosPandion