Mahesha999
Mahesha999

Reputation: 24721

Why these closure based private variables in JavaScript not working

I am experimenting imitating OOP like behavior in JS. I am trying to have (private) variables: id and name in function Person. To this function I am passing arguments which are used to initialize (private) variables. Then I am returning object having getter and setter for name and only a getter for id, thus effectively making id read-only.

So id can be set only through constructor whereas name can be set and get anytime.

This is the code:

     var Person = function (_id,_nm) {
        var id, name;    
                  
        this.id = _id;
        this.name = _nm;

        return {               
            setName: function (nm) {
                name = nm;
            },
            getName: function () {
                return name;
            },
            getId: function () {
                return id;
            },
            print: function () {
                document.writeln("Id: "+id+"<br />Name: "+name);
            }
        }
    }

    var person = new Person(123, "Mahesh");
    person.print();

However when new Person(123,"Mahesh") executes, I dont understand it is actually setting id and name or not, since while debugging I can see values set appropriately when hovered over them but Locals panel does not show them initialized:

Debugging

Or either while in print() is is not referring to the desired id and name variables:

Debugging

Whats wrong here?

Upvotes: 0

Views: 328

Answers (5)

marcoseu
marcoseu

Reputation: 3952

Let me try to explain using the following:

// Scope 1
var Person = function (_id,_nm) {
    // Scope 2
    var id, name;    

    this.id = _id;
    this.name = _nm;

    return {
        // Scope 3
        setName: function (nm) {
            name = nm;
        },
        getName: function () {
            return name;
        },
        getId: function () {
            return id;
        },
        print: function () {
            $("#output").append("<p>Id: "+id+"; Name: "+name  + "<p/>");
        }
    }
}

The print method will return undefined because you are referring to the var id, name; that is never set in your code. You set the _id and _name to the property id and name but you fail to return the object that you just created. Instead, you return a dictionary, that references the name and id variable you created in Scope 2 that you never set, hence the undefined output in the print() call.

Here is what you should have done:

var NewPerson = function (_id,_nm) {
    var self = this;
    this.id = _id;
    this.name = _nm;  

    this.print = function () {
        $("#output").append("<p>New Person Id: "+this.id+"; Name: "+ this.name  + "<p/>");
    };

    return this;
};

var nperson = new NewPerson(456, "Marcos");
nperson.print();

This will output:

New Person Id: 456; Name: Marcos

In essence, new is creating a new object represented by this, and you must return this to have a reference to the object created. In fact, if you do not use the new before creating an object, you end up referencing a global instance of this. Try the following:

var nperson = new NewPerson(456, "Marcos");
this.name = "Hello";
nperson.print();

var nperson1 = NewPerson(456, "Marcos");
this.name = "Hello";
nperson1.print();

You will see that the first output will be as expected:

New Person Id: 456; Name: Marcos

But the second will pick up the change made to this.name:

New Person Id: 456; Name: Hello

Here is the JSFiddle.

Upvotes: 0

Alnitak
Alnitak

Reputation: 339786

You've mixed up using locally scoped ("private") variables for _id and _nm and "public" instance properties (this.id and this.nm).

In this case you need the former, but you created both and only initialised the latter.

Note that since id is read-only you don't really need a separate local variable at all, you can just use the lexically scoped first parameter to the constructor:

var Person = function (id, _nm) {
    var name = _nm;

    ...
};

Upvotes: 1

Aadit M Shah
Aadit M Shah

Reputation: 74204

Either use new or return a value. Not both.

The problem is that you're creating a new instance of Person using the new keyword, but your constructor function is returning another object instead.

When you return something from a constructor function it returns that value, and not the instance of the function.

You see when you execute new Person(123, "Mahesh") a new instance of Person is created. This is accessible within the constructor function as this.

If you don't return anything from your constructor then JavaScript automatically returns this. However you're explicitly returning another object.

No wonder var person doesn't have id and name properties (which you only defined on this).

In addition print doesn't display the id and name because although you declared them (var id, name) you didn't give them any values. Hence they are undefined.

This is how I would rewrite your Person constructor:

function Person(id, name) {
    this.getId = function () {
        return id;
    };

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

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

    this.print = function () {
        document.writeln("Id: " + id + "<br/>Name: " + name);
    };
}

I didn't set the id and name properties on this because it makes no sense to include them.

Upvotes: 1

flavian
flavian

Reputation: 28511

Working fiddle

The reason is because you are using public members for the Person.prototype. You don't need to add this references to these two. So delete:

this.id = _id;
this.name = _nm;

and simply use:

 var id = _id,
     name = _nm;  

Now everything will work fine. The whole idea is to use var, and not this, otherwise a closure will not be created. Now you will not be able to access name and id directly, instead you will have to use setName(), getName(), setId(), getId() etc.

The two members, id and name, will now become closures as you want them to be.

Update

If you used this.id, then it wouldn't have been private and you could just do var p = new Person(1, "Mahesha"); and access p.name or p.id directly. They are supposed to be private so this is not what you want.

With the closure pattern, p.name and p.id are undefined and can only be accessed through p.getName(); and p.getId();. Read on how closures work. The idea is that because you are using that var name, a closure will be created to remember it's value.

Your getName and setName are using that closure to access the name property. There is no this.name, there is a value remembered through a higher - order closure.

Upvotes: 2

Atle
Atle

Reputation: 5447

this.id and var id are not the same. this.id is a property of the object. var id belongs to the local scope.

Upvotes: 1

Related Questions