Inside John Resig's book "Pro Javascript techniques" he describes a way of generating dynamic object methods with the below 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;
};
})();
}
}
The problem is when I try instantiating the above object, the dynamic methods are being attached to the window object instead of the object instantiated. It seems like "this" is referring to the window.
// 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
});
alert( user.getname() );
Running the above code throws this error "user.getname is not a function".
What is the correct way of generating the dynamic functions for each object instantiated?
Is this code from the book? I have the book, but I haven't read through it.
It's an error in the book. Check the errata: http://www.apress.com/9781590597279
Inside the anonymous function, this is the global window.
You could make it work by adding .call(this, i) after it.
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(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);
}
}
The this in the inner self-executing function is not the same as the this in the outer User function. As you noticed, it refers to the global window.
The problem is fixed if you slightly modify the code by adding a variable that refers to the outer this.
function User(properties) {
var self = this;
for (var i in properties) {
(function() {
self["get" + i] = function() { /* ... */ };
self["set" + i] = function() { /* ... */ };
})();
}
}
That said, I'm not sure why the anonymous self-executing function is even needed here, so you have the simpler option of just leaving it out entirely, like this:
function User(properties) {
for (var i in properties) {
this["get" + i] = function() { /* ... */ };
this["set" + i] = function() { /* ... */ };
}
}
Here is how to do it. You need to save the context into another variable. The other option is not to do this inner function that you are doing in the for loop.
// 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)
var that = this;
for ( var i in properties ) { (function(){
// Create a new getter for the property
that[ "get" + i ] = function() {
return properties[i];
};
// Create a new setter for the property
that[ "set" + i ] = function(val) {
properties[i] = val;
};
})(); }
}
Option 2:
// 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 ) {
// 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;
};
}
}
You can always force another this for any function call, using the apply method.
(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;
};
}).apply(this);
You can also make a new function that has uses a certain this with the .bind method.
function getaloadofthis() {return this}
If you do getaloadofthis() it just returns window but if you do getaloadofthis.bind(3)() it returns 3.
So you could also have
const getaloadofthis3 = getaloadofthis.bind(3)
getaloadofthis3() // 3
This also works with anonymous functions
(function() {return this}).bind(3)() // 3
Related
I'm building a function that would retrofit some of my prototypes with some common functions.
I would also like to add object instance specific variables through this mechanic, sorta like:
function give_weird_container(target) {
target.<somehow instance specific, not prototype>.underlying_container = [];
target.prototype.container_func = function(x, y, z) {
return this.underlying_container[x + 2*y + 3*z];
}
}
function my_class1() {}
give_weird_container(my_class1);
And now when I create a new instance of my_class1, it should have a property "uderlying_container" that would act the same as if I called
this.underlying_container = [];
in the constructor.
Is that possible while remaining in the confines of the give_weird_container function?
Is it possible to add a not shared variable to a prototype?
No. All properties on the prototype are shared. Instance specific properties can only be set after an instance was created.
You could however add a getter to the prototype that will create an instance specific property if it doesn't exist.
For example:
Object.defineProperty(target.prototype, 'underlying_container', {
get: function() {
if (!this._underlying_container) {
this._underlying_container = [];
}
return this._underlying_container;
},
});
The getter is shared, but the value returned is per instance.
If you don't like the fact that the getter is executed every time this.underlying_container is accessed, you could replace it with an instance property when the prototype property is called the first time:
Object.defineProperty(target.prototype, 'underlying_container', {
get: function() {
Object.defineProperty(this, 'underlying_container', {value: []});
return this. underlying_container;
},
});
Object.defineProperty(this, 'underlying_container', {value: []}); will create a new property with the same name on the instance thus shadowing the getter defined on the prototype.
To pick up #4castle's suggestion, if it is possible to mutate instances directly, then you could do something like this instead, which is a bit less "magic":
var give_weird_container = (function() {
function container_func(x, y, z) {
return this.underlying_container[x + 2*y + 3*z];
};
return function(target) {
target.underlying_container = [];
target.container_func = container_func;
};
}());
function my_class1() {}
var instance = new my_class1();
give_weird_container(instance);
You could give my_class1 a wrapper function that calls the constructor and then sets the field:
function give_weird_container(target) {
target.prototype.container_func = function(x, y, z) {
return this.underlying_container[x + 2*y + 3*z];
}
return function() {
var obj = new target();
obj.underlying_container = [];
return obj;
}
}
function my_class1() {}
my_class1 = give_weird_container(my_class1);
I want to write a getter and setter functions for an object based on a Json files. I copied the below code of John Resig's book(Appress Pro JavaScript Techniques) but it doesn't work and these functions don't add to the object.Can you tell me why and what is the right 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());
You've used a function to create a closure, but you've forgotten to pass i into it. You'll also need a different reference to this inside the function, as the context changes in it to window.
function User(properties) {
var i, me = this;
for (i in properties) (function (i) {
// Create a new getter for the property
me["get" + i] = function () { // using `me` because `this !== me`
return properties[i];
};
// Create a new setter for the property
me["set" + i] = function (val) {
properties[i] = val;
};
}(i)); // i passed into function closure
}
In your IIFE, this is actually window. You have to specify the context by using Function.prototype.call:
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(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);
}
}
Or keep a reference to it with another variable:
function User(properties) {
var that = this;
// Iterate through the properties of the object, and make sure
// that it's properly scoped (as discussed previously)
for (var i in properties) {
(function(i) {
// Create a new getter for the property
that["get" + i] = function() {
return properties[i];
};
// Create a new setter for the property
that["set" + i] = function(val) {
properties[i] = val;
};
})(i);
}
}
If you were in strict mode, your code would throw an error instead of misleadingly succeeding:
TypeError: Cannot set property 'getname' of undefined
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Why are my JS object properties being overwritten by other instances
Why isn't the attribute "t" changed after setT was called? I would expect "4" as output, but it prints "default".
function Car(i) {
var id = i;
var t = "default";
this.getT = function() { return t; }
this.setT = function(p) {
t = p; // attribute t isn't changed ..
}
}
function ECar(id) {
Car.call(this, id); // super constructor call
this.setT = function(p) { // override
ECar.prototype.setT.call(this, p); // super call
}
}
ECar.prototype = new Car();
ecar = new ECar(3);
ecar.setT(4);
alert(ecar.getT()); // prints default, not 4
ECar.prototype = new Car();
At this line ECar's prototype get a context, in which all ECar's instance will be shared.
ECar.prototype.setT.call(this, p);
This line will call at that context, not what has been created while calling super at Car.call(this, id);.
You can fix your code with
function ECar(id) {
Car.call(this, id); // super constructor call
var carSetT = this.setT;
this.setT = function(p) {
carSetT.call(this, p);
}
}
but it would be better (and more readable) to use real prototypes, such as
function Car() {}
Car.prototype.getT = function () { /* ... */ };
Car.prototype.setT = function () { /* ... */ };
function ECar() {}
ECar.prototype = new Car();
ECar.prototype.setT = function () { /* ... */ };
Edit: note (as #Bergi suggested)
You should only use Child.prototype = new Parent() as inheritance if you must support legacy browsers & then you should only use empty constructors.
The most (other-language) compatible way in JavaScript for inheritance is
Child.prototype = Object.create(Parent.prototype)
(MDN says it is supprted from IE 9)
// attribute t isn't changed ..
Please notice that t is not an "attribute", but a variable local to the constructors scope ("private").
ECar.prototype.setT.call(this, p); // super call
does not work how you expect it. You seem to want to change the variable created with the call to your super constructor (it's still local to that variable environment, and exposed by the getT and setT functions that were created in the constructor. So now, you are calling the function that was created in the line ECar.prototype = new Car(); - which changes the variable t that was created there. That you call the function on the current object does not matter, as it does not use the this keyword inside.
So, you don't want to a) use the method of that prototype Car, but your own and b) don't want to create an instance of Car for the prototype at all. See also What is the reason [not] to use the 'new' keyword here?. To apply the super constructor on your current instance is enough. If you want to extend the methods while still using the old ones, you need to preserve them (and exactly them) in a variable.
function Car(id) {
var t = "default";
this.getT = function () {
return t;
};
this.setT = function (p) {
t = p;
};
}
function ECar(id) {
Car.call(this, id); // super constructor call
var oldSetter = this.setT;
this.setT = function (p) { // override
oldSetter(p); // call the function which access this instance's "t"
}
}
ECar.prototype = Object.create(Car.prototype, {constructor: {value: ECar}});
var ecar = new ECar(3);
ecar.setT(4);
console.log(ecar.getT()); // prints 4
function Car(i) {
var id = i;
var t = "default";
this.getT = function() { return t; }
this.setT = function(p) {
t = p; // attribute t isn't changed ..
}
}
function ECar(id) {
Car.call(this, id); // super constructor call
}
ECar.prototype = new Car();
ECar.prototype.constructor = ECar; //Never forget doing this
ecar = new ECar(3);
ecar.setT(4);
alert(ecar.getT());
You don't need to override setT function.
What is the best design pattern for achieving the following (which doesn't work)?
var obj = (function() {
// code defining private variables and methods
var _obj = {
property: value,
method1: function() {
// do stuff
},
method2: function() {
// use property
var prop = _obj.property; // obviously doesn't work
// call method1
obj.method1(); // "obj" not finished being defined yet!
}
};
// obviously now I could do...
var prop = _obj.property;
return _obj;
})();
// and I could now do...
obj.method1();
A variation which I think should work is
var obj = (function() {
var property = value,
method1 = function() {
// do stuff
},
method2 = function() {
// use property
var prop = property;
// call method1
method1();
},
_obj = {
property: property,
method1: method1,
method2: method2
};
return _obj;
})();
Similarly, how does it work for objects meant to be created with the new operator? Within the constructor function itself you can write this.method(). But what if you want to keep the constructor small, only defining those things which will likely be customized upon creation, and then defining the rest in the prototype? (This seems to be the common pattern.) Can the properties / methods within the prototype interact in any way?
var MyObj = function(name) {
this.name = name;
};
var obj = new MyObj('Bob');
MyObj.prototype = {
called_often: function() {
// lots more code than just the following
return document.getElementById('someID').value;
},
global_default: 'value', // can be changed, so need to pull value when run
does_stuff: function(value) {
var str = global_default + value, // can't access global_default on its own
input = MyObj.called_often(), // doesn't work; MyObj.prototype.called_often() DOES
name = this.name; // 'this' used in the prototype doesn't work
// even within a created object
return name + input + str;
}
};
I'm sure there's better ways to achieve my result whenever I run into this problem. This code isn't situation specific and just illustrates the general problem. So you won't be able to give me an alternative for those specific situations I run into. But maybe you can help my overall thinking.
Well, from your first example:
var _obj = {
property: value,
method1: function() {
// do stuff
},
method2: function() {
// use property
var prop = this.property;
// call method1
this.method1();
}
};
That's what the this value is for.
Now, what you cannot do is refer to a property of an "under construction" object from elsewhere in the object literal syntax. (It's hard to give an example because it's just not syntactically possible.) In cases where you want to do that, you do need one or more separate assignment statements.
Guess what? You are making simple stuff complex. Pointy's answer is good, but the prototype way is better for several reasons. That's why I am describing (rather, making corrections in) the last method. Check this fiddle.
var MyObj = function(name) {
this.name = name;
};
MyObj.prototype = {
called_often: function() {
// lots more code than just the following
return 'VALUE'; //document.getElementById('someID').value;
},
global_default: 'value', // can be changed, so need to pull value when run
does_stuff: function(value) {
var str = this.global_default + value, // can't access global_default on its own
input = this.called_often(), // doesn't work; MyObj.prototype.called_often() DOES
name = this.name; // 'this' used in the prototype doesn't work
// even within a created object
return name + input + str;
}
};
var obj = new MyObj('Bob');
While searching the web, I've encountered a post that shows why the following example of dynamically generated methods does not work as planned:
// 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;
};
})(); }
}
The reason for that is the anonymous function, which uses the "this" keyword is context of the "window", instead of "User".
1) Why does the this keyword in the anonymous function refers to "window instead of "User"?
2) Is there an accepted and common way to create "Dynamically Generated Methods"?
Thanks,
Joel
The reason that this refers to the window object, rather than User, is because this depends on the caller. In this case, the foreach contains an anonymous function that is immediately called. The caller will be considered to be the window object.
The reason it's not working is because the code is poorly written. It would be a simple thing to pass both context and the i-variable to be scoped:
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(x){
// Create a new getter for the property
this[ "get" + x ] = function() {
return properties[x];
};
// Create a new setter for the property
this[ "set" + x ] = function(val) {
properties[x] = val;
};
}).call(this, i); }
}
I did try all your examples, but no one has worked perfectly.
This is the working code:
function User( properties ) {
// Iterate through the properties of the object, and make sure
// that it's properly scoped (as discussed previously)
var that = this;
for ( var i in properties ) { (function(){
// Create a new getter for the property
that[ "get" + i ] = function(i) {
return function (){
console.debug(i);
return properties[i];
}
}(i);
// Create a new setter for the property
that[ "set" + i ] = function(i) {
return function (val){
properties[i] = val;
}
}(i);
})(); }
}
var user = new User({
name: "Bob",
age: 44
});
console.log(user.getname(), user.getage()) //Bob, 44
user.setname("Antonio");
user.setage(33);
console.log(user.getname(), user.getage()) //Antonio, 33
More explanation to the following link
computerone.altervista.org
You need to set a proper reference of the "this" element. You are inside an anonymous scope.
As the first line of the function "User" you should declare a variable like
var _this = this;
Then, instead of calling this[ "get" + i], you have to call _this[ "get" + i]
Try:
// 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)
var self = this;
for ( var i in properties ) { (function(){
// Create a new getter for the property
self[ "get" + i ] = function() {
return properties[i];
};
// Create a new setter for the property
self[ "set" + i ] = function(val) {
properties[i] = val;
};
})(); }
}
The problem there is the missing new operator. If you instance your User without it, the this inside will be window.
This will not work:
var george = User(properties);
This will work:
var george = new User(properties);
This tutorial it's interesting to follow.