Why these closure based private variables in JavaScript not working - javascript

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:
Or either while in print() is is not referring to the desired id and name variables:
Whats wrong here?

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.

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

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.

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;
...
};

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.

Related

Understanding Contexts in Javascript

i'm struggling to understand how contexts work in JavaScript or specifically deriving the value of this. in my case i have Person constructor that returns an object. From my understanding the value of this depends on the way that the function is called. For instance calling the person function like this "Person()" .the value returned for this will be the window. How do i then ensure that the person values passed are exactly what is returned
function Person(personName,Title,Phone,Email,Id, picUrl, description){
var myusername = this.username;
var myTitle = this.Title;
var myPhone = this.Phone;
var myEmail = this.Email;
var myId = this.Id;
var mypicUrl = this.picUrl;
var mydescription = this.description;
return {
name: myusername,
Title: myTitle,
phoneNumber: myPhone,
Email: myEmail,
UserId: myId,
PictureUrl: mypicUrl,
Description: mydescription
}
}
function getallDetails (){
var PeopleCompleteList = [];
for (i=0; i< data.d.results.length; i++) {
if(data.d.results[i]['Name'] != null){
personName = data.d.results[i]['Name'].Name.split('|')[2];
userName = data.d.results[i]['Name']['Name'];
UserTitle = data.d.results[i]['Name']['Title'];
UserphoneNumber = data.d.results[i]['Name']['WorkPhone'];
UserEmail = data.d.results[i]['Name']['EMail'];
Id = data.d.results[i]['Name']['Id'];
picUrl= WebURL + '/_layouts/15/userphoto.aspx?size=L&accountname='+UserEmail;
description = data.d.results[i]['pozb'];
PeopleCompleteList.push(Person(personName, UserTitle, UserphoneNumber,UserEmail,Id, picUrl, description));
}
}
}//end function
As #CBroe and #Icepickle said in comments, you are not using the Person function as a constructor. You need to call your method with the new keyword. By doing so :
the this keyword inside your function will be a reference to the object you're willing to create (otherwise it would be a reference to the global object)
the function doesn't need to return anything as it implicitly returns the object referenced by this
I'd advise you read this article : MDN - the new operator.
Meanwhile, the snippet below shows what your code would look like if you used the Person function as a constructor
function Person(personName, title, phone, email, id, picUrl, description) {
// populating the object behind the 'this' reference with each argument
// if you call this method with the 'new' keyword, 'this' will be a reference to the instance you are creating
this.username = personName;
this.title = title;
this.phoneNumber = phone;
this.email = email;
this.userId = Id;
this.pictureUrl = picUrl;
this.description = description;
// if this function is called with the 'new' keyword, you don't have to return anything. the reference behind 'this' will be implicitly returned
}
function getallDetails() {
// local variable declarations are usually put on top of the function
var peopleCompleteList = [], personName, userName, userTitle, userphoneNumber, userEmail, id, picUrl, description;
for (i=0; i< data.d.results.length; i++) {
if (data.d.results[i]['Name'] != null) {
personName = data.d.results[i]['Name'].Name.split('|')[2];
userName = data.d.results[i]['Name']['Name'];
userTitle = data.d.results[i]['Name']['Title'];
userphoneNumber = data.d.results[i]['Name']['WorkPhone'];
userEmail = data.d.results[i]['Name']['EMail'];
id = data.d.results[i]['Name']['Id'];
picUrl= WebURL + '/_layouts/15/userphoto.aspx?size=L&accountname='+UserEmail;
description = data.d.results[i]['pozb'];
// adding the 'new' keyword before invoking the Person method
peopleCompleteList.push(new Person(personName, userTitle, userphoneNumber,userEmail,Id, picUrl, description));
}
}
}//end function
Couple side notes :
You forgot to use the var keyword when declaring variables in the getallDetails function. It's a bad practice as they will all be global variables and won't be cleared after the execution of the getallDetails function
Most of your variables declarations violate the JavaScript variable naming convention. I updated the snippet with proper variable names (using the camelCase notation).
In simple terms, You can say context is the place where this reside. For example:
<script>
function show() {
this.context = 'sudhir';
}
</script>
This is equivalent to
<script>
var context;
function show() {
this.context = 'sudhir';
}
</script>
Both functions are written on the global scope. So, this refers to global means scope or your context is global. Now let's see the interesting example:
<script>
function newContext() {
var a = function show() {
this.context = 'sudhir';
}
}
</script>
It's equivalent to write here like this:
<script>
function newContext() {
var context;
var a = function show() {
this.context = 'sudhir';
}
}
</script>
Here this refers to the function show which is written on the scope of newContext function. Hence, Context of this is newContext.
I will show you what I will have done.
First of all, if you create an object, you don't have to return a value.
function Person(personName,Title,Phone,Email,Id, picUrl, description){
this.personName = personName;
[...]
this.mydescription = description;
}
Then you instantiate your "class" like :
var person = new Person('David', ..., 'some description');
And access attribute like that :
console.log('Person name is', person.personName);
JavaScript "This" is about function scope, example :
function Person(personName,Title,Phone,Email,Id, picUrl, description){
this.personName = personName;
[...]
this.mydescription = description;
function someAnonymousFunction(){
console.log('Person name is', this.personName); // Person name is Undefined
// Because this is a reference to someAnonymousFunction
}
}
Javascript trick to preserve this is to stock the context in variable :
function Person(personName,Title,Phone,Email,Id, picUrl, description){
var self = this;
this.personName = personName;
[...]
this.mydescription = description;
function someAnonymousFunction(){
console.log('Person name is', self.personName); // Person name is David
// Because we use self variable instead of this here
}
}
But you have an easier way
ECMAScript 2015 includes classes syntax. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes
If you care about navigator compatibility, you must use TypeScript
https://www.typescriptlang.org/
Typescript allow you to write more readable JavaScript code, especially about classes. Then, you can "compile" your code in JavaScript by choosing with version of JavaScript you want in output files.
Hope it helps,
Mickael

Adding a method to an object that is inside of a function in js

Hi this is from a challenge I was working on. Is there any way i can add the introduce method to the personStore object without using the keyword this. Any insight is greatly appreciated.
Using Object.create
Challenge 1/3
Inside personStore object, create a property greet where the value is a function that logs "hello".
Challenge 2/3
Create a function personFromPersonStore that takes as input a name and an age. > When called, the function will create person objects using the Object.create method on the personStore object.
Challenge 3/3
Without editing the code you've already written, add an introduce method to the personStore object that logs "Hi, my name is [name]".
Side Curiosity
As a side note, was curious if there was a way to add the introduce method to the person object that sits inside of the personFromPersonStore function.
my solution:
var personStore = {
// add code here
greet: function (){
console.log('Hello');
}
};
function personFromPersonStore(name, age) {
var person = Object.create(personStore);
person.name = name;
person.age = age;
person.greet = personStore.greet;
return person;
};
personStore.introduce = function () {
console.log('Hi, my name is ' + this.name)
}
//Challenge 3 Tester
sandra.introduce(); // -> Logs 'Hi, my name is Sandra
You can, but using this is a lot simpler.
This code passes the name property as an argument, but as the property is already accessible to the introduce function as an internal property via this, it is a bit wasteful.
var personStore = {
// add code here
greet: function (){
console.log('Hello');
}
};
function personFromPersonStore(name, age) {
var person = Object.create(personStore);
person.name = name;
person.age = age;
person.greet = personStore.greet;
return person;
};
personStore.introduce = function (nm) {
console.log('Hi, my name is ' + nm)
}
person1=personFromPersonStore('Fred',21);
person1.introduce(person1.name);
You can write it like this:
personFromPersonStore("whatevername","whateverage").name
instead of this.

Javascript private variables and parameters have same name

function() {
var name;
this.set = function( name ) {
this.name = name; <-- this
}
}
How would I write the line so that I can assign the parameter to the private variable? Unlike Java, "this" also points to a function, so I can't figure it out.
You'll just have to use a different name for the argument so it won't shadow the private variable name outside the function set, that's all.
var SomeClass = function() {
var name;
this.set = function( otherName ) { // argument should have a diffirent name so it won't shadow the other variable "name"
name = otherName;
}
this.get = function() {
return name;
}
};
var ins = new SomeClass();
ins.set("Jim Carrey"); // OK
console.log(ins.get()); // OK
console.log(ins.name); // undefined as expected
Inside the function set, inner name variable will get more preference than the outer name variable. In order to assign some value to the outer variable, you will have to have a different name inside or outside.
However, You can do something like,
function() {
this.name = '';
this.set = function( name ) {
this.name = name; <-- this
}
}
This is not assigning the new value to the private variable, this is assigning the value to the object's property.
2020 edit:
I happen to come back to this question and realized that I was mistaken.
Better code example.
type = function() {
var name = "default";
var self = this;
this.set = function(name) {
console.log("this: " + this);
console.log("self: " + self);
console.log("name: " + name);
name = name; // <-- name shadowed
}
this.get = function() {
return name;
}
}
test = new type();
console.log("test.name: " + test.name);
console.log("test.get: " + test.get());
test.set("new")
console.log("test.name: " + test.name);
console.log("test.get: " + test.get());
I wanted to avoid name shadowing which can be done in Java by doing
this.name = name;
However, in Javascript, "name" is declared as a public property and initialized so that defeats the purpose of this thought-exercise.
"self" technique won't work either (this is where I mistakenly thought it would work)
The true answer is Aadit M Shah's comment in the first answer.
With the recent EcmaScript (or not so recent), Javascript lets you assign getter and setter to a property, so this technique is probably not needed.

Is it possible to add value to a new property of an object in js?

In js I have created an object. I want to add a new property to the object's prototype and the property will be different from instance to instance.
Now to add value I used get. But it gives me error. I have added the code below.
How can I accomplish this thing?
I have googled this. And all I have learned is by get they add value to an existing property. But I want to add value to the new property and that will vary instance to instance.
var computer = function (name, ram) {
this.name = name;
this.ram = ram;
};
Object.defineProperty(computer.prototype, "graphic", {
set: function graphic(value) {
this.graphic = value;
},
get: function graphic() {
return this.graphic;
},
});
var vio = new computer("sony", "8gb");
vio.graphic = "gtx980";
console.log(vio.graphic);
The error massage:
enter image description here
Rereading your question, I'll answer the actual concern:
When you put things on the prototype, they are shared between all instances (as though you add them to the class in a classical language like Java).
When you put things on this, they are only accessible for the specific instance.
The following works, without setters or getters:
function Computer(name, ram) { // Please use Capital names for constructors
this.name = name;
this.ram = ram;
};
let vio = new Computer('sony', '8gb');
vio.graphic = 'gtx980';
The graphic property will only exist for the instance held in vio, not every computer instance out there.
If, on the other hand you were to do this:
function Computer(name, ram) {
this.name = name;
this.ram = ram;
}
Computer.prototype.graphic = 'gtx980';
// All instances of Computer will now have a .graphic with the value of 'gtx980'.
The reason you're getting the error is that you define a setter for graphic, in it, you're trying to assign to graphic which invokes the setter for graphic which tries to assign to graphic which invokes.... you get the point.
The solution is to change the name of the actual variable (to, say _graphic).
var computer = function (name, ram) {
this.name = name;
this.ram = ram;
};
Object.defineProperty(computer.prototype, "graphic", {
set: function graphic(value) {
this._graphic = value;
},
get: function graphic() {
return this._graphic;
},
});
var vio = new computer("sony", "8gb");
vio.graphic = "gtx980";
console.log(vio.graphic);
Note that JS doesn't really have private variables. You won't be able to prevent someone from changing _graphic.

Trouble Defining a Class in JavaScript

I am trying to learn how to define a class in JavaScript. I found this link (http://www.phpied.com/3-ways-to-define-a-javascript-class/) but it doesn't seem to meet my needs. Essentially, I want to have a class that has properties and functions. When a class is initialized, I want to automatically call the init function. At this time, when I create a new Item using the following code, I get an error:
var item = new Item();
The error says: Object has no method 'init'.
My class definition looks like the following:
function Item() {
this.id = null;
this.name = "";
this.description = "";
this.init();
this.init = function () {
this.id = "54321";
};
}
What am I doing wrong? How do I create a constructor in JavaScript?
You're calling init() before you've defined it. Simply move the invocation below the definition:
function Item() {
this.id = null;
this.name = "";
this.description = "";
this.init = function () {
this.id = "54321";
};
this.init();
}
As you are instantiating the Item class, you could use the prototype of Item to take some of the logic out of the constructor. In the example below, Item is instantiated with all default properties from the prototype (including the init function)
function Item() {
this.init();
}
Item.prototype = {
id: null,
name: '',
description: '',
init: function () {
this.id = "54321";
}
};
There are many ways to manipulate the prototype - here it is set to an Object to set all properties at once, but it could have had each item added individually by using Item.prototype.someVariable = something style syntax.
One note on setting the prototype - any non-primitive data type (ie: not boolean, number or string) will be shared across all instances (useful for sharing functions like the init function) whereas primitives will be per instance.
You are calling the init method before it's defined. Only FunctionDeclarations in JavaScript are hoisted. Not FunctionExpressions assigned to a variable. Remember, only declarations are hoisted. Not definitions.

Categories

Resources