I have an Object with a getter and setter where I'm intending to protect a property so that it can only be updated through a method. Let's call this object Person which has the following structure:
function Person(data) {
var _name = data.name;
this.name = function () {
return _name;
};
this.setName = data.setName || function (newValue) {
_name = newValue;
};
}
I want to be able to override setName and pass different implementations to each instance of Person but I can't quite seem to get it to work. I'm certain this is a closure issue, but I just can't get my head around it.
Given the following usage scenario, what am I doing wrong?
var p1 = new Person({
name: "Paul",
setName: function (newValue) {
this._name = newValue;
}
});
var p2 = new Person({ name: "Bob" });
p1.setName("Paul (updated)");
p2.setName("Bob (updated)");
p1 never updates its value and so is always "Paul" whereas p2 does and becomes "Bob (updated)". I want to be able to create as many unique instances of Person as I want, where some of them have their own implementation of setName and others will just use the default instance.
I've tried wrapping data.setName up in a closure like this and setting my custom setName to return the value instead:
this.setName = data.setName ? function () {
return (function (value) {
value = data.setName();
}(_name));
} : function (newValue) { _name = newValue; }
But I'm having no luck - I obviously just don't get closures as well as I thought! Any and all help always appreciated.
codepen.io example here
this._name = newValue;
_name is a private variable, not a property. It cannot be accessed on this. Have a look at Javascript: Do I need to put this.var for every variable in an object? if you're not sure about the difference.
function (value) {
value = data.setName();
}
value here is a local variable to that function's scope, not a "reference" to the _name variable you passed in. Setting it will only put a different value in that local variable, but not change anything else.
Possible solutions:
Pass a setter function that has access to the _name variable to the
validator:
function Person(data) {
var _name = data.name;
this.name = function () {
return _name;
};
if (data.nameValidator)
this.setName = function(newValue) {
_name = data.nameValidator(newValue, _name);
};
else
this.setName = function (newValue) {
_name = newValue;
};
}
var p1 = new Person({
name: "Paul",
nameValidator: function (newValue, oldValue) {
return (newValue /* ... */) ? newValue : oldValue;
}
});
Or let the validator return the value:
function Person(data) {
var _name = data.name;
this.name = function () {
return _name;
};
this.setName = function (newValue) {
_name = newValue;
};
if (data.makeNameValidator)
this.setName = data.makeNameValidator(this.setName);
}
var p1 = new Person({
name: "Paul",
makeNameValidator: function (nameSetter) {
return function(newValue) {
if (newValue) // ...
nameSetter(newValue);
};
}
});
Or make _name a property to which the validator can write. You could also put this on the original data object, for not exposing it on the Person interface:
function Person(data) {
this.name = function () {
return data.name;
};
if (data.setName)
this.setName = data.setName.bind(data);
else
this.setName = function(newValue) {
_name = newValue;
};
}
var p1 = new Person({
name: "Paul",
setName: function (newValue) {
if (newValue) // ...
this.name = newValue;
}
});
I feel this is the simplest solution so far, using bind:
function Person(data) {
var _data = {name:data.name};
this.name = function () {
return _data.name;
};
this.setName = data.setName ? data.setName.bind(_data) : function (newValue) {
_data.name = newValue;
};
}
var p1 = new Person({
name: "Paul",
setName: function (newName) {
this.name = newName;
}
});
var p2 = new Person({ name: "Bob" });
p1.setName("Paul (updated)");
p2.setName("Bob (updated)");
console.log(p1.name());
console.log(p2.name());
Instead of using a primitive to store the name we use an object, whose reference we can pass as this using bind. Originally, when you wrote this._name = newValue;, this meant something quite different from what you thought, namely the object you pass the intantiate the new Person.
demo
You are setting this._name in your p1 Person, whereas you are accessing the 'internal' variable _name in your Person() function, instead of this._name. This can easily be fixed by adding this. to parts in your Person() class.
function Person(data) {
this._name = data.name;
this.name = function () {
return this._name;
};
this.setName = data.setName || function (newValue) {
this._name = newValue;
};
}
Your second example, with the wrapping, is not working due to some errors you appear to have made in the setName() function. Firstly, you are not passing anything to the data.setName() function, meaning that it is receiving undefined for the first parameter. Next up, you are setting value to the returned value, whereas you should instead be setting _name to the returned value. Try this instead:
this.setName = data.setName ? function (newValue) {
return (function (value) {
_name = data.setName();
}(newValue));
} : function (newValue) { _name = newValue; }
U think it's a problem with this
this.setName = function (newValue) {
if (data.setName) {
data.setName.call(this, newValue);
} else {
_name = newValue;
}
}
Related
When using get in an object like this, get works:
var people = {
name: "Alex",
get sayHi() {
return `Hi, ${this.name}!`
}
};
var person = people;
document.write(person.sayHi);
But with a function I get an error. How to use Getters and Setters in a function like this?
function People2() {
this.name = "Mike";
get sayHi() {
return `Hi, ${this.name}!`;
}
};
var user = new People2();
document.write(user.sayHi);
You can use the actual get and set keywords only in classes (ES2015) and object literals.
ECMAScript 5
In ES5, your would typically use Object.defineProperty to implement what you're trying to achieve:
function People2() {
this.name = "Mike";
}
Object.defineProperty(People2.prototype, "sayHi", {
get: function() {
return "Hi, " + this.name + "!";
}
});
ECMAScript 2015
In ES2015, you could also use classes to achieve the desired behavior:
class People2 {
constructor() {
this.name = "Mike";
}
get sayHi() {
return `Hi, ${this.name}!`;
}
}
You can try this
<script>
function People2(name) {
this.name = name;
};
People2.prototype = {
get sayHi() {
return `Hi, ${this.name}!`;}
};
var user = new People2('Alex');
document.write(user.sayHi);
</script>
or this one...
<script>
function people(name) {
this.name = name;
};
Object.defineProperty(people.prototype, 'sayHi', {
get: function() { return `Hi, ${this.name}!`; }
});
var person = new people('Alex');
document.write(person.sayHi);
</script>
For the case you want to define a property like as name for a function with more control, we can use Object.defineProperty on function itself as following:
function people(name) {
//this.name = name; //this can be modified freely by caller code! we don't have any control
var _name = name; //use a private var to store input `name`
Object.defineProperty(this, 'name', {
get: function() { return _name; }, //we can also use `return name;` if we don't use `name` input param for other purposes in our code
writable: false, //if we need it to be read-only
//... other configs
});
};
var person = new people('Alex');
console.log(person.name); //writes Alex
For example, use this:
function People2() {
this.name = "Mike";
this.__defineGetter__("sayHi", function() {
return `Hi, ${this.name}!`;
});
};
I have a JavaScript class like this:
Dog = (function() {
var name;
function setName(_name) {
name = _name;
}
return {
setName: setName,
name: name
};
})();
When I run:
Dog.setName('Hero');
Dog.name is always undefined.
I am certainly missing something about JS scoping, but what?
You are returning an object where name property has a value of name at that point in time (which is undefined). The name property of the returned object is not somehow dynamically updated when the name variable inside the IIFE is updated.
There are many ways to handle what you appear to be wanting to do. Here's one:
Dog = (function() {
var name;
function setName(_name) {
name = _name;
}
return Object.defineProperties({}, {
setName: { value: setName },
name: { get: function() { return name; } }
});
})();
This keeps name as a private variable, which can only be set via setName, but provides a getter property for obtaining its value.
The alternative proposed in another answer is equivalent, just a different way of writing it:
return {
setName: function(n) { name = n; },
get name: function() { return name; }
};
Minor point, but in this particular context you don't need parentheses around your IIFE:
Dog = function() { }();
will work fine.
This happens because you assume that setting name in the object retains a reference to the original name variable. Instead, you want to assign it to the current object (which, you might as well ignore the private variable altogether).
Dog = {
name: '',
setName: function(n) {
this.name = n;
}
};
However, if you want to keep name private then you create a getter for it instead.
var Dog = (function() {
var name;
return {
setName: function(n) {
name = n;
},
get name: function() {
return name;
}
};
})();
The easy way to fix this is:
Dog = (function() {
var dog = {
setName: setName,
name: name
};
function setName(_name) {
dog.name = _name;
}
return dog;
}
In your code, you were setting the wrong name variable.
var name;
function setName(_name) {
name = _name;
}
In this function, setName is setting the internal variable name and not the property name. In JavaScript, strings are immutable, so when you change it, it creates a new string, and doesn't update the existing one.
This might be a better pattern for you. You're using the very old ES3 style constructor.
(function(exports) {
function Dog(name) {
this.name = name;
}
Dog.prototype.speak = function() {
return "woof";
};
// other functions ...
exports.Dog = Dog;
})(window);
var d = new Dog('Hero');
console.log(d.name); // "Hero"
You might want to look into ES6 classes too
class Dog {
constructor(name) {
this.name = name;
}
}
let d = new Dog('Hero');
console.log(d.name); // "Hero"
Sounds like you want to make a constructor... Check this sample:
function Dog(prop) {
this.name = prop.name;
this.color = prop.color;
}
var myDog = new Dog({
name:'Sam',
color: 'brown'
});
alert()
console.log('my dog\'s name is: '+myDog.name);
console.log('my dog\'s color is: '+myDog.color);
you can try it here: http://jsfiddle.net/leojavier/ahs16jos/
I hope this helps man...
Use the 'this' keyword.
Dog = (function() {
var name;
function setName(_name) {
this.name = _name;
}
return {
setName: setName,
name: name
};
})();
Dog.setName('Hero');
alert(Dog.name);
$(document).ready(function () {
var patient = (function (options) {
var age = options.age;
var name = options.name;
function getName() {
return this.name;
}
function setName(val) {
name = val;
}
function getAge() {
return this.age;
}
function setAge(val) {
age = val;
}
return {
getAge: getAge,
setAge: setAge,
getName: getName,
setName: setName
}
})();
});
I realize that I'm never passing any options in my example here.
If I try to do something like patient.setAge('100') and then console.log(patient.getAge()) I get an error saying cannot read property Age of undefined. The overarching theme that I'm trying to get at is within a module, how can I emulate consturctors to instantiate a new patient object while keeping all the OOP goodness of private variables and all that jazz.
I've seen some examples of constructors in a module pattern on here and I haven't understood them very well. Is it a good idea in general to have a constructor in a module? Is its main purpose similarity with class-based languages?
This is a constructor:
function Patient(options) {
options = options || {};
this.age = options.age;
this.name = options.name;
}
$(document).ready(function () {
var patient = new Patient();
});
You can put it inside a module if you want. What you shouldn’t do is provide getters and setters, especially ones that don’t do anything. If you’re exposing a variable through two properties to get and set it, it should just be one property.
Try this
function Patient (options) {
options = options || {};
var age = options.age;
var name = options.name;
function getName() {
return name;
}
function setName(val) {
name = val;
}
function getAge() {
return age;
}
function setAge(val) {
age = val;
}
return {
getAge: getAge,
setAge: setAge,
getName: getName,
setName: setName
}
}); // pass empty object
$(document).ready(function () {
var p1 = new Patient({});
var p2 = new Patient();
var p3 = new Patient({age:20});
var p4 = new Patient({name:"abcd"});
var p5 = new Patient({age:21, name:"abcd"});
});
I am building a javascript library where I have to create a log of classes and most of them have a lot of properties which have to make public for the user.
For example:
function Person(name,age){
}
Now I want to create the getter and setter for properties (name and age).
Nornall, I have to add these methods to Person.prototype:
Person.prototype.getName=function(){}
Person.prototype.setName=function(x){
//check if x is typeof String
}
Person.prototype.getAge=function(){}
Person.prototype.setAge=function(x){
//check if x is typeof Number
}
This will result in two many lines of repeated codes.
So I wonder if I can call a method like this:
makeThesePropertiesPublic(Person,{
name:"string",
age:"number"
});
Then I can call this:
var p=new Person("xx",1);
p.getName();
p.getAge();
.......
Is there a out-of-box method to implement this?
First of all you can't define the getter and setter functions on the prototype because they need to be able to access name and age which are only accessible inside the constructor. Hence you would need to define the getter and setter functions inside the constructor.
I would do this:
function Person(name, age) {
var private = {
name: name,
age: age
};
Object.defineProperties(this, {
name: getAccessor(private, "name", "String"),
age: getAccessor(private, "age", "Number")
});
}
function getAccessor(obj, key, type) {
return {
enumerable: true,
configurable: true,
get: function () {
return obj[key];
},
set: function (value) {
if (typeOf(value) === type)
obj[key] = value;
}
};
}
function typeOf(value) {
return Object.prototype.toString.call(value).slice(8, -1);
}
Now you can access create a Person and access their name and age properties as follows:
var person = new Person("Aadit M Shah", 20);
person.name = 0; // it won't set the name
person.age = "twenty"; // it won't set the age
alert(person.name);
alert(person.age);
See the demo: http://jsfiddle.net/aVM2J/
What about something like this?
function Person(n, a) {
var name = n;
var age = a;
var person = {};
person.getName = function () {
return name
}
person.getAge = function () {
return age;
}
return person;
}
var p = Person("jon",22);
console.log(p.getName());//jon
Consider this simple JavaScript module pattern:
var human = (function () {
var _name = '';
return {
name: _name,
setName: function (name) {
_name = name;
}
}
})();
human.setName('somebody');
alert(human.name); // shows an empty string
human = (function () {
var _name = '';
return {
name: function() {
return _name;
},
setName: function (name) {
_name = name;
}
}
})();
human.setName('somebody');
alert(human.name()); // shows 'somebody'
Why the second closure works fine, while the first closure is not working? See example here.
Please also see this fiddle, which proves that simple properties can be used instead of getter functions.
In Javascript
Strings and primitive types (boolean and numeric) are passed by value
Objects, arrays, and functions are passed by reference
As name is a string name: _name will store the current value of _name and not the reference to _name.
setName in your example will modify only _name.
getName will access _name which holds the current value.
.name will access the copied value which was set during the initialisation (name: _name).
See also SO: Javascript by reference vs. by value
Try with this:
var human = (function () {
var _name = '';
var returnObj = {};
returnObj.name = _name;
returnObj.setName = function (name) {
_name = name;
returnObj.name = name;
};
return returnObj;
})();
human.setName('somebody');
alert(human.name);
The problem with your code was that setName was assigning a value to the _name variable and you ware accessing the name property of the returned object.