I'm reading "Pro JavaScript Techniques" by John Resig, and I'm confused with an example. This is the code:
// Create a new user object that accepts an object of properties
function User( properties ) {
// Iterate through the properties of the object, and make sure
// that it's properly scoped (as discussed previously)
for ( var i in properties ) { (function(){
// Create a new getter for the property
this[ "get" + i ] = function() {
return properties[i];
};
// Create a new setter for the property
this[ "set" + i ] = function(val) {
properties[i] = val;
};
})(); }
}
// Create a new user object instance and pass in an object of
// properties to seed it with
var user = new User({
name: "Bob",
age: 44
});
// Just note that the name property does not exist, as it's private
// within the properties object
alert( user.name == null );
// However, we're able to access its value using the new getname()
// method, that was dynamically generated
alert( user.getname() == "Bob" );
// Finally, we can see that it's possible to set and get the age using
// the newly generated functions
user.setage( 22 );
alert( user.getage() == 22 );
Now running that in the Firebug console (on Firefox 3) throws that user.getname() is not a function. I tried doing this:
var other = User
other()
window.getname() // --> This works!
And it worked!
Why?
Doing:
var me = this;
seems to work a bit better, but when executing "getname()" it returns '44' (the second property)...
Also I find it strange that it worked on the window object without modification...
And a third question, what's the difference between PEZ solution and the original? (He doesn't use an anonymous function.)
I think it's best not to use the new keyword at all when working in JavaScript.
This is because if you then instantiate the object without using the new keyword (ex: var user = User()) by mistake, *very bad things will happen...*reason being that in the function (if instantiated without the new keyword), the this will refer to the global object, ie the window...
So therefore, I suggest a better way on how to use class-like objects.
Consider the following example :
var user = function (props) {
var pObject = {};
for (p in props) {
(function (pc) {
pObject['set' + pc] = function (v) {
props[pc] = v;
return pObject;
}
pObject['get' + pc] = function () {
return props[pc];
}
})(p);
}
return pObject;
}
In the above example, I am creating a new object inside of the function, and then attaching getters and setters to this newly created object.
Finally, I am returning this newly created object. Note that the the this keyword is not used anywhere
Then, to 'instantiate' a user, I would do the following:
var john = user({name : 'Andreas', age : 21});
john.getname(); //returns 'Andreas'
john.setage(19).getage(); //returns 19
The best way to avoid falling into pitfalls is by not creating them in the first place...In the above example, I am avoiding the new keyword pitfall (as i said, not using the new keyword when it's supposed to be used will cause bad things to happen) by not using new at all.
Adapting Jason's answer, it works:
We need to make a closure for the values. Here's one way:
function bindAccessors(o, property, value) {
var _value = value;
o["get" + property] = function() {
return _value;
};
o["set" + property] = function(v) {
_value = v;
};
}
Then the User constructor looks like this:
function User( properties ) {
for (var i in properties ) {
bindAccessors(this, i, properties[i]);
}
}
You probably want something like this, which is more readable (closures are easy to learn once you get some practice):
function User( properties ) {
// Helper function to create closures based on passed-in arguments:
var bindGetterSetter = function(obj, p, properties)
{
obj["get" + p] = function() { return properties[p]; }
obj["set" + p] = function(val) { properties[p]=val; return this; }
};
for (var p in properties)
bindGetterSetter(this, p, properties);
}
I also added "return this;", so you can do:
u = new User({a: 1, b:77, c:48});
u.seta(3).setb(20).setc(400)
I started this post with the sole purpose of learning why that things happened, and I finally did. So in case there's someone else interested in the "whys", here they are:
Why does 'this' changes inside the anonymous function?
A new function, even if it is an anonymous, declared inside an object or another function, always changes the scope, in this case returning to the global scope (window).
Solution: all stated in the post, I think the clearer is executing the anonymous function with .call(this).
Why does getname() always return the age?
While the anonymous function gets executed right away, the getters/setters get executed for the first time when they are called. In that moment, the value of i will always be the last, because it has already iterated for all the properties... and it will always return properties[i] which is the last value, in this case the age.
Solution: save the i value in a variable like this
for ( i in properties ) { (function(){
var j = i
// From now on, use properties[j]
As written in the OP, this in the loop is not referring to the User object as it should be. If you capture that variable outside the loop, you can make it work:
function User( properties ) {
// Iterate through the properties of the object, and make sure
// that it's properly scoped (as discussed previously)
var me = this;
for ( i in properties ) { (function(){
// Create a new getter for the property
me[ "get" + i ] = function() {
return properties[i];
};
// Create a new setter for the property
me[ "set" + i ] = function(val) {
properties[i] = val;
};
// etc
I just modified the code a bit like this.. This one should work.. This is same as setting me=this; But a closure is required to set the value of each property properly, else the last value will be assigned to all properties.
// Create a new user object that accepts an object of properties
var User = function( properties ) {
// Iterate through the properties of the object, and make sure
// that it's properly scoped (as discussed previously)
var THIS = this;
for ( var i in properties ) { (function(i){
// Create a new getter for the property
THIS[ "get" + i ] = function() {
return properties[i];
};
// Create a new setter for the property
THIS[ "set" + i ] = function(val) {
properties[i] = val;
};
})(i); }
}
// Create a new user object instance and pass in an object of
// properties to seed it with
var user = new User({
name: "Bob",
age: 44
});
// Just note that the name property does not exist, as it's private
// within the properties object
alert( user.name == null );
// However, we're able to access its value using the new getname()
// method, that was dynamically generated
alert( user.getname() == "Bob" );
// Finally, we can see that it's possible to set and get the age using
// the newly generated functions
user.setage( 22 );
alert( user.getage() == 22 );
Maybe the variable i is "closured" with the last value in the iteration ("age")? Then all getters and setters will access properties["age"].
I found something that seems to be the answer; it’s all about context. Using the anonymous function inside the for loop, changes the context, making 'this' refer to the window object. Strange isn't it?
So:
function User(properties) {
for (var i in properties) {
// Here this == User Object
(function(){
// Inside this anonymous function, this == window object
this["get" + i] = function() {
return properties[i];
};
this["set" + i] = function(val) {
properties[i] = val;
};
})();
}
}
I don't know why that function changes the context of execution, and I'm not sure it should do that. Anyway, you can test it running the code there and trying window.getname(). It magically works! :S
The solution, as stated before, is changing the context. It can be done like J Cooper said, passing the variable 'me' and making the function a closure or you can do this:
(function(){
// Inside this anonymous function this == User
// because we called it with 'call'
this[ "get" + i ] = function() {
return properties[i];
};
this["set" + i] = function(val) {
properties[i] = val;
};
}).call(this);
Anyway, I'm still getting 44 when running 'getname'... What could it be?
Related
I'm creating a GAS Spreadsheets Service based app that reads/writes & updates a row of data. I have a key-value object that represents a row of data, like the example data provided in snippet.
Use case:
var exampleData = [{weekendVolume=5186270,midweekVolume=16405609}];
// tuple length 2 of two known values
function _DataRecordObject( exampleData ) {
this._endOfWeek = new Date().endOfWeek();// Date.prototype method
}
var _DataRecordMethods = {
weekEnding: function() {
return this._endOfWeek.formatDateString()
},
weekMonth: function() {
return this._endOfWeek.getMonthLabelShort()
},
/* Processed volume */
weekendVolume: function() {
return 'weekendVolume'
},
midweekVolume: function() {
return 'midweekVolume'
},
totalVolumeProcessed: function() {
return _SumTotal(
this.weekendVolume(),
this.midweekVolume()
)
}
}
_DataRecordObject.prototype = _DataRecordMethods;
The new DataRecordObject is prototype of a Sheet object that provides other helpful properties. _SumTotal is a helper function.
My question:
When I call a new DataRecordObject with sheet range as argument, how do I update the exampleData object with the new properties such as totalVolumeProcessed?
For example:
var foo = new _DataRecordObject( exampleData );
Console.log( foo );
//[{weekEnding='Aug-17',weekMonth=4,weekendVolume=5186270,midweekVolume=16405609,totalVolumeProcessed=21591879}]
I'd like the flexibility of using constructor-prototype inheritence, but using a boilerplate style template like Object-literals. My intuition suggests that I need to pass the data object keys when constructing a new dataRecordObject.
I'm a newcomer to JavaScript and have not yet gotten my head around inheritance, prototypes, and respective design-patterns. Factories and Modules, or perhaps Observers seem like appropriate patterns but my limited experience with JS is a limiting factor to solving my problem.
This might work for you.
1) Define the prototype as an object literal:
var methods = {
sayName: function() {
return "My name is " + this.name;
},
sayAge: function() {
return "I am " + this.age + " years old";
}
};
2) You can either make the 'methods' variable global or define it inside the following function. The function creates a new object using 'methods' variable as a prototype and populates it with values from the 'data' argument.
function createNewObj (data) {
var data = data || null;
var result = Object.create(methods);
for (var key in data) {
if (data.hasOwnProperty(key)) {
result[key] = data[key];
}
}
return result;
}
3) Bringing things together
function test() {
var data = {name: "John", age: "32"};
var row = createNewObj(data);
Logger.log(row.name); //logs 'John'
Logger.log(row.age); //logs '32'
Logger.log(row.sayName()); //logs 'My name is John'
Logger.log(row.sayAge()); //logs 'I am 32 years old'
Logger.log(Object.getPrototypeOf(row)); // returns contents of the 'methods' object literal
Logger.log(row.hasOwnProperty("sayName")); //logs 'false' because 'hasOwnProperty' doesn't go up the prototype chain
Logger.log("sayName" in row); //logs 'true' because 'in' goes up the chain
}
I suggest you take a look at this blog post by Yehuda Katz that dives deeper into prototypes http://yehudakatz.com/2011/08/12/understanding-prototypes-in-javascript/ It has examples of much cleaner code that might be helpful.
I've found a solution, which expands on #Anton-Dementiev 's response. His suggestion to read the Yehudi Katz was also most helpful.
The create new object function, _DataRecordObject is where the solution lies..
function _DataRecordObject( RowDataObject ) {
this._endOfWeek = new Date().endOfWeek();// Date.prototype method
var data = RowDataObject || null;
var result = Object.create( _DataRecordMethods );
for (var key in data) {
if ( data.hasOwnProperty( key ) ) {
// if value is present in the RowDataObject,
// then assign its value to the result
result[key] = data[key];
} else {
// if not, the value is a method function,
// which should be evaluated in that context,
// and then return the method value as result
var foo = Object.getPrototypeOf( result )[ key ];
result[key] = foo.call( data );
}
}
return result;
}
//simples
Since the methods are passed as property functions, they need to be called as functions in that context.
I am following a tutorial to create getter and setter in Javascript, I have the code like this:
// Create a new User object that accept an object of properties
function User(properties) {
// Iterate through the properties of the object, and make
// sure it's properly scoped
for (var i in properties) { (function(){
// Create a new getter for the property
this['get' + i] = function() {
return properties[i];
};
// Create a new setter for the property
this['set' + i] = function(val) {
properties[i] = val;
};
})(); }
}
// Create a new User object instance and pass in an object of
// properties to seed it with
var user = new User({
name: 'Bob',
age: 28
});
// Just note that the name property does not exist, as it's private
// within the property object
console.log(user.name == null);
// However, we are able to access its value using the new getname()
// method, that was dynamically generated
console.log(user.getname());
However, console shows error saying user does not have method getname. The code is trying to dynamically generate getter and setter method, it looks ok to me. Any thoughts?
The other answers are correct in that you need to pass i into your anonymous function, but you could also do this with ES5 Getters and Setters:
// Create a new User object that accept an object of properties
function User(properties) {
var self = this; // make sure we can access this inside our anon function
for (var i in properties) {
(function(i) {
Object.defineProperty(self, i, {
// Create a new getter for the property
get: function () {
return properties[i];
},
// Create a new setter for the property
set: function (val) {
properties[i] = val;
}
})
})(i);
}
}
The benefit of using ES5 getters and setters is that now you can do this:
var user = new User({name: 'Bob'});
user.name; // Bob
user.name = 'Dan';
user.name; // Dan
Since they're functions, they modify the passed in properties, not just the object itself. You don't have to use getN or setN anymore, you can just use .N, which makes using it look more like accessing properties on an object.
This approach, however, isn't universally portable (requires IE 9+).
Here's what I'd probably do in practice though:
function User(properties) {
Object.keys(properties).forEach(function (prop) {
Object.defineProperty(this, prop, {
// Create a new getter for the property
get: function () {
return properties[prop];
},
// Create a new setter for the property
set: function (val) {
properties[prop] = val;
}
})
}, this);
}
The above gets rid of your for loop. You're already using an anonymous function, so might as well get the most of it.
Probably a closure issue:
function User(properties) {
// Iterate through the properties of the object, and make
// sure it's properly scoped
for (var i in properties) {
(function(i){
// Create a new getter for the property
this['get' + i] = function() {
return properties[i];
};
// Create a new setter for the property
this['set' + i] = function(val) {
properties[i] = val;
};
}.call(this, i));
}
}
Also, as #Paul pointed out, this was actually referring to the function which was contained in the for loop. Not the User function. I fixed that by using a call and assigning the User this to the function (no need for extra variable).
Your in-loop function is losing the this, do a var t = this; outside loop and refer to t inside. Also, pass i into your function.
function User(properties) {
var t = this, i;
for (i in properties) (function (i) {
t['get' + i] = function () { return properties[i]; };
t['set' + i] = function (val) { properties[i] = val; };
}(i));
}
This is something which has been bugging me with the Google Chrome debugger and I was wondering if there was a way to solve it.
I'm working on a large Javascript application, using a lot of object oriented JS (using the Joose framework), and when I debug my code, all my classes are given a non-sensical initial display value. To see what I mean, try this in the Chrome console:
var F = function () {};
var myObj = new F();
console.log(myObj);
The output should be a single line which you can expand to see all the properties of myObj, but the first thing you see is just ▶ F.
My issue is that because of my OO framework, every single object instantiated gets the same 'name'. The code which it looks is responsible for this is like so:
getMutableCopy : function (object) {
var f = function () {};
f.prototype = object;
return new f();
}
Which means that in the debugger, the initial view is always ▶ f.
Now, I really don't want to be changing anything about how Joose instantiates objects (getMutableCopy...?), but if there was something I could add to this so that I could provide my own name, that would be great.
Some things that I've looked at, but couldn't get anywhere with:
> function foo {}
> foo.name
"foo"
> foo.name = "bar"
"bar"
> foo.name
"foo" // <-- looks like it is read only
Object.defineProperty(fn, "name", { value: "New Name" });
Will do the trick and is the most performant solution. No eval either.
I've been playing around with this for the last 3 hours and finally got it at least somewhat elegant using new Function as suggested on other threads:
/**
* JavaScript Rename Function
* #author Nate Ferrero
* #license Public Domain
* #date Apr 5th, 2014
*/
var renameFunction = function (name, fn) {
return (new Function("return function (call) { return function " + name +
" () { return call(this, arguments) }; };")())(Function.apply.bind(fn));
};
/**
* Test Code
*/
var cls = renameFunction('Book', function (title) {
this.title = title;
});
new cls('One Flew to Kill a Mockingbird');
If you run the above code, you should see the following output to your console:
Book {title: "One Flew to Kill a Mockingbird"}
Combine usage of computed property name to dynamically name a property, and inferred function naming to give our anonymous function that computed property name:
const name = "aDynamicName"
const tmp = {
[name]: function(){
return 42
}
}
const myFunction= tmp[name]
console.log(myFunction) //=> [Function: aDynamicName]
console.log(myFunction.name) //=> 'aDynamicName'
One could use whatever they want for 'name' here, to create a function with whatever name they want.
If this isn't clear, let's break down the two pieces of this technique separately:
Computed Property Names
const name = "myProperty"
const o = {
[name]: 42
}
console.log(o) //=> { myProperty: 42 }
We can see that the property name assigned on o was myProperty, by way of computed property naming. The []'s here cause JS to lookup the value inside the bracket, and to use that for the property name.
Inferred Function Naming
const o = {
myFunction: function(){ return 42 }
}
console.log(o.myFunction) //=> [Function: myFunction]
console.log(o.myFunction.name) //=> 'myFunction'
Here we use inferred function naming. The language looks at the name of wherever the function is being assigned to, & gives the function that inferred name.
We can combine these two techniques, as shown in the beginning. We create an anonymous function, which gets it's name via inferred function naming, from a computed property name, which is the dynamic name we wanted to create. Then we have to extract the newly created function from the object it is embedded inside of.
Example Using Stack Trace
Naming a supplied anonymous function
// Check the error stack trace to see the given name
function runAnonFnWithName(newName, fn) {
const hack = { [newName]: fn };
hack[newName]();
}
runAnonFnWithName("MyNewFunctionName", () => {
throw new Error("Fire!");
});
Although it is ugly, you could cheat via eval():
function copy(parent, name){
name = typeof name==='undefined'?'Foobar':name;
var f = eval('function '+name+'(){};'+name);
f.prototype = parent;
return new f();
}
var parent = {a:50};
var child = copy(parent, 'MyName');
console.log(child); // Shows 'MyName' in Chrome console.
Beware: You can only use names which would be valid as function names!
Addendum: To avoid evaling on every object instantiation, use a cache:
function Cache(fallback){
var cache = {};
this.get = function(id){
if (!cache.hasOwnProperty(id)){
cache[id] = fallback.apply(null, Array.prototype.slice.call(arguments, 1));
}
return cache[id];
}
}
var copy = (function(){
var cache = new Cache(createPrototypedFunction);
function createPrototypedFunction(parent, name){
var f = eval('function '+name+'(){};'+name);
f.prototype = parent;
return f;
}
return function(parent, name){
return new (cache.get(name, parent, typeof name==='undefined'?'Foobar':name));
};
})();
This won't totally solve your problem, but I would suggest overriding the toString method on the class's prototype. For instance:
my_class = function () {}
my_class.prototype.toString = function () { return 'Name of Class'; }
You'll still see the original class name if you enter an instance of my_class directly in the console (I don't think it's possible to do anything about this), but you'll get the nice name in error messages, which I find very helpful. For instance:
a = new my_class()
a.does_not_exist()
Will give the error message: "TypeError: Object Name of Class has no method 'does_not_exist'"
If you want to dynamically create a named function. You can use new Function to create your named function.
function getMutableCopy(fnName,proto) {
var f = new Function(`function ${fnName}(){}; return ${fnName}`)()
f.prototype = proto;
return new f();
}
getMutableCopy("bar",{})
// ▶ bar{}
Similar to #Piercey4 answer, but I had to set the name for the instance as well:
function generateConstructor(newName) {
function F() {
// This is important:
this.name = newName;
};
Object.defineProperty(F, 'name', {
value: newName,
writable: false
});
return F;
}
const MyFunc = generateConstructor('MyFunc');
const instance = new MyFunc();
console.log(MyFunc.name); // prints 'MyFunc'
console.log(instance.name); // prints 'MyFunc'
normally you use window[name] like
var name ="bar";
window["foo"+name] = "bam!";
foobar; // "bam!"
which would lead you to a function like:
function getmc (object, name) {
window[name] = function () {};
window[name].prototype = object;
return new window[name]();
}
but then
foo = function(){};
foobar = getmc(foo, "bar");
foobar; // ▶ window
foobar.name; // foo
x = new bar; x.name; // foo .. not even nija'ing the parameter works
and since you can't eval a return statement (eval("return new name()");), I think you're stuck
I think this is the best way to dynamically set the name of a function :
Function.prototype.setName = function (newName) {
Object.defineProperty(this,'name', {
get : function () {
return newName;
}
});
}
Now you just need to call the setName method
function foo () { }
foo.name; // returns 'foo'
foo.setName('bar');
foo.name; // returns 'bar'
foo.name = 'something else';
foo.name; // returns 'bar'
foo.setName({bar : 123});
foo.name; // returns {bar : 123}
Based on the answer of #josh, this prints in a console REPL, shows in console.log and shows in the debugger tooltip:
var fn = function() {
return 1917;
};
fn.oldToString = fn.toString;
fn.toString = function() {
return "That fine function I wrote recently: " + this.oldToString();
};
var that = fn;
console.log(that);
Inclusion of fn.oldToString() is a magic which makes it work. If I exclude it, nothing works any more.
With ECMAScript2015 (ES2015, ES6) language specification, it is possible to dynamically set a function name without the use of slow and unsafe eval function and without Object.defineProperty method which both corrupts function object and does not work in some crucial aspects anyway.
See, for example, this nameAndSelfBind function that is able to both name anonymous functions and renaming named functions, as well as binding their own bodies to themselves as this and storing references to processed functions to be used in an outer scope (JSFiddle):
(function()
{
// an optional constant to store references to all named and bound functions:
const arrayOfFormerlyAnonymousFunctions = [],
removeEventListenerAfterDelay = 3000; // an auxiliary variable for setTimeout
// this function both names argument function and makes it self-aware,
// binding it to itself; useful e.g. for event listeners which then will be able
// self-remove from within an anonymous functions they use as callbacks:
function nameAndSelfBind(functionToNameAndSelfBind,
name = 'namedAndBoundFunction', // optional
outerScopeReference) // optional
{
const functionAsObject = {
[name]()
{
return binder(...arguments);
}
},
namedAndBoundFunction = functionAsObject[name];
// if no arbitrary-naming functionality is required, then the constants above are
// not needed, and the following function should be just "var namedAndBoundFunction = ":
var binder = function()
{
return functionToNameAndSelfBind.bind(namedAndBoundFunction, ...arguments)();
}
// this optional functionality allows to assign the function to a outer scope variable
// if can not be done otherwise; useful for example for the ability to remove event
// listeners from the outer scope:
if (typeof outerScopeReference !== 'undefined')
{
if (outerScopeReference instanceof Array)
{
outerScopeReference.push(namedAndBoundFunction);
}
else
{
outerScopeReference = namedAndBoundFunction;
}
}
return namedAndBoundFunction;
}
// removeEventListener callback can not remove the listener if the callback is an anonymous
// function, but thanks to the nameAndSelfBind function it is now possible; this listener
// removes itself right after the first time being triggered:
document.addEventListener("visibilitychange", nameAndSelfBind(function(e)
{
e.target.removeEventListener('visibilitychange', this, false);
console.log('\nEvent listener 1 triggered:', e, '\nthis: ', this,
'\n\nremoveEventListener 1 was called; if "this" value was correct, "'
+ e.type + '"" event will not listened to any more');
}, undefined, arrayOfFormerlyAnonymousFunctions), false);
// to prove that deanonymized functions -- even when they have the same 'namedAndBoundFunction'
// name -- belong to different scopes and hence removing one does not mean removing another,
// a different event listener is added:
document.addEventListener("visibilitychange", nameAndSelfBind(function(e)
{
console.log('\nEvent listener 2 triggered:', e, '\nthis: ', this);
}, undefined, arrayOfFormerlyAnonymousFunctions), false);
// to check that arrayOfFormerlyAnonymousFunctions constant does keep a valid reference to
// formerly anonymous callback function of one of the event listeners, an attempt to remove
// it is made:
setTimeout(function(delay)
{
document.removeEventListener('visibilitychange',
arrayOfFormerlyAnonymousFunctions[arrayOfFormerlyAnonymousFunctions.length - 1],
false);
console.log('\nAfter ' + delay + 'ms, an event listener 2 was removed; if reference in '
+ 'arrayOfFormerlyAnonymousFunctions value was correct, the event will not '
+ 'be listened to any more', arrayOfFormerlyAnonymousFunctions);
}, removeEventListenerAfterDelay, removeEventListenerAfterDelay);
})();
I have not seen anyone mention the use of ES6 Proxies. Which in my opinion solve this problem beautifully. So here it is.
function shadow(object, secondObject) {
return new Proxy(object, {
get(target, prop, receiver) {
if (secondObject.hasOwnProperty(prop)) return secondObject[prop];
return Reflect.get(...arguments);
}
})
}
let t=function namedFunction(a,b,c){return a+b+c;}
console.log(t.name)//read only property
let f=shadow(t,{name:"addition"})
console.log(f.name)
I am trying to create a UserDon object, and trying to generate the get and set methods programmatically ( based on Pro Javascript book by John Resig page 37 ), and am testing this on Firefox 3.5
The problem is: in function UserDon, "this" refers to the window object instead of the UserDon object.
So after calling var userdon = new UserDon(...) I got setname and getname methods created on the window object (also setage and getage).
How can I fix this?
function UserDon( properties ) {
for( var i in properties ) {
(function(){
this[ "get" + i ] = function() {
return properties[i];
};
this[ "set" + i ] = function(val) {
properties[i] = val;
};
})();
}
}
var userdon = new UserDon( {
name: "Bob",
age: 44
});
The this value you are using belongs to the auto-invoking function expression you have inside the loop, and when you invoke a function in this way, this will always refer to the global object.
Edit: I missed the fact that the function expression is trying to make variable capturing to handle the getter/setter creation inside the loop, but the looping variable i, needs to be passed as an argument in order to do it and since the function expression is there, context (the outer this) should be preserved:
function UserDon( properties ) {
var instance = this; // <-- store reference to instance
for( var i in properties ) {
(function (i) { // <-- capture looping variable
instance[ "get" + i ] = function() {
return properties[i];
};
instance[ "set" + i ] = function(val) {
properties[i] = val;
};
})(i); // <-- pass the variable
}
}
var userdon = new UserDon( {
name: "Bob",
age: 44
});
userdon.getname(); // "Bob"
userdon.getage(); // 44
You can also use the call method to invoke the function expression, preserving the context (the value of this) and introducing the looping variable to the new scope in a single step:
function UserDon( properties ) {
for( var i in properties ) {
(function (i) { // <-- looping variable introduced
this[ "get" + i ] = function() {
return properties[i];
};
this[ "set" + i ] = function(val) {
properties[i] = val;
};
}).call(this, i); // <-- preserve context and capture variable
}
}
I would also recommend to use an if (properties.hasOwnProperty(i)) { ... } inside the for...in loop to avoid iterating over user extended properties inherited from Object.prototype.
You can also use the lesser-known
__defineGetter__("varName", function(){});
and __defineSetter__("varName", function(val){});
Although they are nonstandard [like x-html-replace content-type] they are supported by a majority of the non-ie browsers out there [chrome, firefox]
Syntax would be:
benjamin = new object();
benjamin.__defineGetter__("age", function(){
return 21;
});
Or you can approach this with prototyping
benjamin = {
get age()
{
return 21;
}
}
It could be a better idea to get a generic function with 2 parameters : the name of the property and the value (or only the name for the getter).
And this function would check the presence of a speial function for this property and if there isn't any, it would juste change the property's value (or return it's value for getter).
Here is how I would code it:-
function UserDon( properties ) {
var self = this;
for( var i in properties ) {
(function(prop){
self[ "get" + prop ] = function() {
return properties[prop];
};
self[ "set" + prop ] = function(val) {
properties[prop] = val;
};
})(i);
}
}
Please look at my required JavaScript.
var someVariable = new SomeDataType();
// I can directly access value of its property.
someVariable.someProperty = "test";
alert(someVariable.someProperty); // <- this command must should "test"
// However, I have some methods in above property
// I want to validate all rule in this property.
someVariable.someProperty.isValid(); // <- this method will return true/false
Is it possible for doing this in current version of JavaScript?
UPDATE
Please look as my answer!
Yes, you can assign Javascript functions as properties like this:
someVariable.someProperty = function (arg1, arg2) {
// function code goes here
};
This is the method using function literals.
Another method is to use function instances like this:
someVariable.someProperty = new Function (arg1, arg2, code);
Note that in the second method, the code goes in as the last parameter and the Function keyword has a capitalized 'F' as against method 1 where the 'f' is small.
Further, creating a function instance inside a loop etc. will create an entire new instance to assign which is inefficient in memory. This problem does not arise while using the function literal method.
You can't (and probably shouldn't) treat objects like that in JavaScript. As someone else mentioned, you can override the toString() method to get half of the functionality (the read portion), but you cannot use the assignment operator on an object like that without overwriting the object.
You should choose a different approach, like using nested objects (as CMS suggested).
Its possible, but with the below change in your code
function SomeDataType(){
var localProperty="";
this.someProperty = function(txt){
if (arguments.length==0)
return localProperty;
else
localProperty=txt;
}
this.someProperty.isValid = function(){
return (localProperty!="") ? true : false;
};
}
instead of defining someProperty as a property, define this as function which sets value to the local property if any value is passed or it ll return that property value if no argument is given.
var someVariable = new SomeDataType();
someVariable.someProperty("test");
alert(someVariable.someProperty());
var isValid = someVariable.someProperty.isValid();
this is how you need to access the SomeDataType object.
someVariable.someProperty = [ test, anotherFunc, yetAnotherFunc];
someVariable.somePropertyAllValid= function() {
for(var prop in someVariable.someProperty) {
if(!prop()) return false;
}
return true;
};
someVariable.somePropertyAllValid();
I just found the answer. It's very simple & clean.
function createProperty(value, defaultValue, ruleCollection)
{
this.value = value;
this.defaultValue = defaultValue;
this.ruleCollection = ruleCollection;
}
createProperty.prototype.toString = function()
{
return this.value;
};
var someVariable =
{
someProperty: new createProperty
(
'currentValue',
'defaultValue',
null
)
};
For testing, you can use something like my following code.
var test = ">>" + someVariable.someProperty + "<<";
// this alert must shows ">> currentValue <<"
alert(test);
someVariable =
{
someProperty: new createProperty
(
7,
5,
null
)
};
test = someVariable.someProperty + 3;
// This alert must shows "10"
alert(test);
I just test it on FF 3.5 & IE 8. It works fine!
Update
Oops! I forget it. Because this technique returns object reference for this property. So, it's impossible to directly set property data. It isn't my final answer.
Perhaps this would be of help:
var SomeVar = {
someProperty : {
value : 7,
add : function (val) {
this.value += parseInt(val, 10);
return this;
},
toString : function () {
return this.value;
}
}
}
alert(SomeVar.someProperty.add(3));