I’ve been trying to create a wrapper function that takes all the function properties of an object (obj) and wraps them with another function ( p() ).
This code illustrates what I mean.
//Setup code
function p(input){
//do stuff
return new_output;
}
obj = {
prop1: function(){...},
prop2: function(){...},
prop3: function(){...}
}
//Here's the wrapper function
r = new R(obj);
//Expected behaviour
r.prop1(a1,a2); //Just like saying p(obj.prop1(a1,a2))
Here’s my attempt at the implementation
function R (obj) {
for (var member in obj) {
//Mirrors obj's members
this[member] = function (args) {
var fn,inner_member = member;
//Convert to array for 'apply'
args = Array.prototype.slice.call(args,0);
fn = obj[member];
//Returns unexpected values, poo...
console.log(inner_member);
return p( fn.apply(fn,args) );
}
};
}
Unfortunately, whenever I run r.prop1(), the console, via console.log, returns the wrong member, “prop3” and obj[member] returns “obj.prop3”. Everything is wrong.
I think this has to do with the closure and how the newly created member member functions are looking outside their scope.
How do I fix this?
EDIT: David Flanagan’s Javascript: The Definitive Guide answers this question pretty much directly in Chapter 8, section 3. That section is on closures and the last example mirrors what I wrote above. The key to understanding my problem is that functions are invoked in the same scope chain as they’re defined. A function is an object AND related scope chain. AKA, a closure.
The
membervariable referenced by the functions you’re generating in theforloop is the samemembervariable.This is because JavaScript does not have block scope, just function scope. It’s a common issue.
One solution is to invoke a function in the loop, passing in
memberso that it becomes part of a new variable scope.Now the value of
memberin each iteration in the loop is pass as an argument into a separate function invocation, which creates a new variable scope each time.Because each new function is part of each unique variable scope, each one is referencing a different
memvariable (or parameter).There are other variations on the same concept:
This is the same, except that it returns a function from the invocation to be assigned to
this[ member ]. Same principle though.Other people prefer to use an IIFE (immediately invoked function expression) instead of a named function.
…though I think it’s less clear, and a little less efficient.
EDIT: Added
vartomembervariable to avoid implicit global. Thanks to @CMS.EDIT: Changed
Array.prototype.slice(args,0)toArray.prototype.slice.call(args,0)EDIT:
This isn’t related to any issue, but if all you’re doing is passing on the
argumentswhen the wrapper invokes the original function, you can get rid of…And just pass on the original
argumentsobject:No need to convert it to an Array.