1

Sorry I'm probably being a realy noob here...but:

I have the following javascript object:

jeeni.TextField = (function(){

    var tagId;

    privateMethod = function(){
        console.log("IN: privateMethod");
    }

    publicMethod = function(){
        console.log("IN: publicMethod: " + this.tagId);
    }

    jeeni.TextField = function(id){
        console.log("Constructor");
        this.tagId = id;
    }

    jeeni.TextField.prototype = {
            constructor: jeeni.TextField,
            foo: publicMethod
    };

    return jeeni.TextField;
 }());

Now when I run the following code I get the corresponding result:

var textField1 = new jeeni.TextField(21); // Outputs: Constructor
textField1.foo();           // Outputs: IN: publicMethod: 21
console.log(textField1.tagId); // Outputs: 21
console.log(textField1.privateMethod); // Outputs: undefined

So my question is why is privateMethod hidden and tagId is not. I want them both to be private scope.

Please help a noob.

Thanks

1
  • The tagId you're accessing is public, not private => this.tagId = id Commented Nov 25, 2012 at 22:17

4 Answers 4

2

You mixed up two ways of creating modules. The problem is that the private var tagId is not the same thing as this.tagId

Let me start with a version that works and is similiar to what I normally do with AMD modules:

jeeni.TextField = (function(){
//here you can put a variable common to all instances
  return {
  init:function(id){
    var tagId = id;
    console.log("Constructor");

    function privateMethod(){
        console.log("IN: privateMethod");
    }

    function publicMethod(){
        console.log("IN: publicMethod: " + tagId);
    }

    return {
       foo:publicMethod
    };
  }
})();

var textField1 = jeeni.TextField.init(21); //creates instance
textField1.foo();           // Outputs: IN: publicMethod: 21
console.log(textField1.tagId); // Outputs: undefined
console.log(textField1.privateMethod); // Outputs: undefined

This has one disadvantage: for every object instance the functions are copied in the memory. That's the only reason to use prototypes. But if you want private variables, you'll probably waste some RAM anyway.

In your code, if you replace this.tagId with just tagId you'll be using the private variable, but it will be just one, common for all instances.

I will think about making your code work and edit if I find a way to do that.

[edit]

What Stephen did is close to what you expected your code to do. I wouldn't want to exlain how it works and why to a co-worker though.

[edit]

BTW. Take a look at require.js and AMD (module definition)

Sign up to request clarification or add additional context in comments.

3 Comments

Thanks for the help. But I read in JavaScript Patterns that you should use prototype to help memory. Is this a none-issue? If so what is the point in prototypes? Thanks again for the reply.
There is no reason to create new functions everytime. In this case publicMethod and privateMethod could be outside the init() function and it would work the same without the waste of memory. By virtue of that, the init() function would be made redundant and then you're back to the OP's pattern :) See my answer.
And how would they use different values of private variable? See my comment under your answer.
1

Your code is almost right. Just lose this when accessing tagId. this.tagId is setting a property on that particular TextField, not the var tagId you declared up top.

If you set a variable x without using var, as in x = 2, it finds the nearest x going up the scope chain; if none is found it ends up being a property of the global object (window). In your case you can modify tagId from any of those functions since they can reach it one level up. That's why it's used as a "private" variable, the same way privateMethod can be reached.

And since jeeni.TextField is being set to the return value from the whole function, you don't to reference jeeni on the inside.

jeeni.TextField = (function(){

    var tagId;

    var privateMethod = function(){
        console.log("IN: privateMethod");
    }

    var publicMethod = function(){
        console.log("IN: publicMethod: " + tagId);
    }

    function TextField(id){
        // this === the new instance of TextField
        console.log("Constructor", this);
        // set the tagId variable which is in scope here
        tagId = id;
    }

    TextField.prototype.foo = publicMethod

    return TextField

 }()); 

Note that I also added var before the public/private methods, otherwise you are leaking them to the global scope. You could just as well use function publicMethod(){ ... }, which is usually better for debugging.

In case you're confused, x = function(){}... is not the same as function x(){}. See chapter 2 of "Named function expressions demystified".

With that cleared up, this is still probably not what you want, since with this code tagId will be shared between all instances of TextField. The usual method of having one tagId per instance would be making it a "public" property, which is exactly what you had done (I don't see any problem with this, since id itself comes from the outside):

...

// var tagId -> drop this

publicMethod = function(){
    console.log("IN: publicMethod: " + this.tagId);
}

function TextField(id){
    console.log("Constructor");
    this.tagId = id;
}

Now let's make tagId private and specific for each instance. The simplest way to do it, without creating new functions every time, is to create a private object (let's call it tags) that holds all the data, and give each instance it's own ID (tf_id). The ID is visible, but since the tags object is private you can only access the data by using one of the public methods:

jeeni.TextField = (function(){

    var tags = {}
      , uid = 0;

    privateMethod = function(){
        console.log("IN: privateMethod");
    }

    publicMethod = function(){
        console.log("IN: publicMethod: " + tags[this.tf_id].tagId);
    }

    function TextField(id){
        this.tf_id = uid++
        tags[this.tf_id] = id
    }

    TextField.prototype.foo = publicMethod

    return TextField

 }());

8 Comments

var textField1 = new jeeni.TextField(21); var textField2 = new jeeni.TextField(23); textField1.foo() logs: IN: publicMethod: 23
@naugtur I posted the answer before it was complete. Sorry.
I see you edited. This time you create a public variable that guards the integrity of instances. So textField1.tf_id=textField2.tf_id or any tampering with public variable hurts the private parts [pun intended :) ]
@naugtur yes, but this is javascript, and why would you mess with the object's id? You're not getting 100% tamper-safe objects unless you use Object.freeze(). Or you can waste lots of memory like in your answer, which is going to blow up in a large project.
I don't like where this is going. Your solution is not maintainable and in fact uses a module to pretend to be instances. "Anyway, It's javascript" is not an excuse. I see you prefectly understand what is going on with instances, memory and closures, but I wouldn't recommend that for teams.
|
0

Apparently I read the question incorrectly.

If you set a property of something (i.e. this.tagId) it immediately becomes accessible to the world. You've wrapped your constructor in the wrong spot - change it to something like this instead:

jeeni.TextField = (function(){
    var TextField = function(id){
        console.log("Constructor");
        var tagId = id;
        privateMethod = function(){
            console.log("IN: privateMethod");
        }

        this.publicMethod = function(){
            console.log("IN: publicMethod: " + tagId);
        }
    }

    TextField.prototype = {
            constructor: TextField
    };

    return TextField;
}());

Basically, you won't be able to put anything on the prototype that requires access to those protected variables.

6 Comments

Sorry - sorted the private bit with the above tip. But...I don't want one instance replacing the value of the previous instance. How do I make tagId an instance variable?
Now you write to the same object twice.
Ianzz, no need to be hostile. After posting I did catch that the poster was doing something differently from what I had expected. @AdamDavies - try the updated setup.
@Stephen Now it works. Still looks a bit weird to me - not too obvious when you read it.
Thanks everyone. No need to worry about hostilities. I'm coming at this from a Java background so I've spent years struggeling with frameworks that try to put java on the client (GWT, JSF, RichFaces, etc). So working with a native client API like JavaScript is a real breath of fresh air. Just got to get my head around that funky JavaScript philosophy. Thanks for all your help.
|
0

There are a few problems here. The first is that you are messing about with global variables in a not-normal way. Effectively you have this:

var globalVar;

globalVar = function() {
    globalVar = function() {
        //... stuff
    }
    return globalVar;
}

What are you hoping that will do? The normal way to write this is:

function myObj(id) {
    // in javascript the function *is* the constructor
    tagId = id;
    function someMethod() {
        //...
    }
}
var globalVar = new myObj(someId);

Although it's better to avoid globals.

In the example you give, this is the actual object you're returning:

 function(id){
    console.log("Constructor");
    this.tagId = id;
}

So no, it doesn't have a privateMethod or publicMethod for that matter. Since this function object is constructed in the scope of the enclosing function, the constructor could access privateMethod, which is where the privacy of it comes from.

To see how to do private variables and methods in javascript, go to the wizard himself:

http://javascript.crockford.com/private.html

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.