I'm trying to procedurally add getters/setters to objects in Javascript and although I think the code below should simply work, it doesn't act as I expected.
here is my code:
var data = {a:1, b:2, c:3};
function Abc(data) {
var data = data || {};
for ( var key in data ) {
console.log(key, data[key]);
this.__defineGetter__(key, function() {
console.log('using getter');
return data[key];
})
}
return this;
}
abc = Abc(data);
console.log('this should be 1', abc.a);
console.log('this should be 2', abc.b);
console.log('this should be 3', abc.c);
and this is my unexpected output
a 1
b 2
c 3
using getter
this should be 1 3
using getter
this should be 2 3
using getter
this should be 3 3
the output makes absolutely no sense to me, but I get the same output on Chrome and Webkit, so I'm guessing I'm just stupid and this is not a bug of the Javascript engines :)
as the comments mention my triple开发者_开发技巧 use of "data" isn't really good!
By the time the closure that was passed to __defineGetter__
is executed, the loop has finished and key
remains at the last value. Try this:
function Abc(data) {
if(!(this instanceof Abc)) {
return new Abc(data);
}
data = data || {};
for(var key in data) {
(function(key) {
console.log(key, data[key]);
this.__defineGetter__(key, function() {
console.log('using getter');
return data[key];
});
}).call(this, key);
}
return this;
}
Some other things:
- You weren't using
var
onkey
, sokey
was global. That wasn't causing your issue, but it's a good idea to add it anyway. - You were declaring a new variable named
data
when there was already adata
in the scope of the function. It's not needed to usevar
there since there's already adata
in the function; I removed it. - You weren't using
new
, which was also causing odd behavior; the new three lines at the top of the function cause it to act as if it was called withnew
.
@Robert Gould. main problem in your code sample is not in "defining getters and setters", but in "understanding closures".
- See more about closures in MDN
Additionaly your code is invalid on using this keyword, because your Abc() function this object points to global window object. You must use new Abc() to properly use this keyword or must create new empty object inside Abc() and return it.
1)
function Abc(data) {
data=data||{};
for(key in data) {
console.log(key, data[key]);
(function(data,key) { // defining closure for our getter function
this.__defineGetter__(key, function() {
console.log('using getter');
return data[key];
});
}).call(this,data,key);
}
// dont need to return: when used *new* operator *this* is returned by default
}
var abc = new Abc(data);
or 2)
function Abc(data) {
data=data||{};
var obj={};
for(key in data) {
console.log(key, data[key]);
(function(data,key) { // defining closure for our getter function
obj.__defineGetter__(key, function() {
console.log('using getter');
return data[key];
});
})(data,key);
}
return obj; // return object
}
var abc = Abc(data);
If you realy need to known about "defining getters|setterns" read about:
- Object.defineProperty() MDN and get operator MDN
- and for back compatibility __defineGetter__ MDN
- you must also understand what not all top used browsers currently supports getters and setters for properties
key is a local variable in the scope of Abc
, the anonymous function you wrote has no variable key, so it uses the one from the outer scope. That variable has changed to the last value in the loop by the time the anonymous function is used, so you see the last value. You can fix this with a closure:
this.__defineGetter__(key, (function(l_key){
return function() {
console.log('using getter');
return data[l_key];
}
})(key));
In that code l_key
is a local copy of key
to the anonymous function returned be another anonymous function.
精彩评论