Please consider the following code:
<html>
<head>
<script type="text/javascript">
function a(){
var v = 9;
var w = 2;
var x = 7;
var template = '{w} + {x} = {v}';
var func = eval('(' + c.toString() + ')');
func(template);
}
function b(){
var v = 1;
var y = 'hello';
var z = 'world';
var template = '{v}. {y} {z}';
var func = eval('(' + c.toString() + ')');
func(template);
}
function c(template){
var re = /{(.+?)}/;
var match = template.match(re);
while (match != null){
template = template.replace(re, eval(match[1]));
match = template.match(re);
}
alert(template);
}
</script>
</head>
<body>
<input type="button" value="a" onclick="a()"/><br/>
<input type="button" value="b" onclick="b()"/><br/>
</body>
</html>
This code has two functions (a and b) and a parsing function c that receives a string template as parameter and parses it, using variables that are scoped in the calling function (a or b).
This means that function c has to 'know' all the variables that are known to whichever function was calling it.
What I want is for c to 'know' all the variables in the scope of its caller.
My solution was this line of code in a and b:
var func = eval('(' + c.toString() + ')');
What this does is redefine c as func inside the calling function, so in effect making it a sub function of the ca开发者_如何学运维ller and thus bringing it into the same scope.
This solution works great, but the problem with it is that it's ugly. I have to turn c into a string and re-eval it to a function every time I want to use it. I'm hoping someone can suggest a better solution, if such exists.
I don't want to pass all the variables as parameters to c because:
- The template to parse can be very big and include anywhere from1 to dozens of variables.
- If I pass all the variables as parameters to c and access them using the arguments array in c it means I have to use array notation inside the template which is bad practice for obvious reasons.
- Putting all the variables into a hash map object and passing that object as parameter to c is possible, but makes for a huge coding overhead to create this hash map from the caller's variables before any call to c.
Note: Please don't bother pointing out to me that the parsing function is not perfect, it's just a simplified example of my actual code.
You're overcomplicating things. You can eliminate the need to cross scopes by packing your replacement values as an object rather than as individual variables, and using the g
flag and a replacement function allow you to greatly simplify c()
. Give this a try:
function a(){
var values = {
v: 9,
w: 2,
x: 7
};
func(c('{w} + {x} = {v}', values));
}
function b(){
var values = {
v: 1,
y: 'hello',
z: 'world'
};
func(c('{v}. {y} {z}', values));
}
function c(template, values) {
return template.replace(/{(.*?)}/g, function(match) {
return values[match[1]];
});
}
After playing with it for a while, this is the closest I've been able to come to passing the local scope into another function. It's seriously hacky, involves a fair bit of code duplication, and still needs eval()
(though not as much), but it may be what you're looking for.
Basically, this involves declaring all of your local variables as function parameters (instead of using var
statements) so that their names can be extracted by converting the function back to source via .toString()
. These parameters are not supplied when calling a()
and b()
!
(Note that the c()
function here is identical to the one in my other answer.)
rxArgs = /^[^(]+\(([^)]+)\)/;
function a(v, w, x){
v = 9;
w = 2;
x = 7;
var args = rxArgs.exec(arguments.callee.toString())[1].split(", ");
var i = args.length, values = {};
while (i--) values[args[i]] = eval(args[i]);
func(c('{w} + {x} = {v}', values));
}
function b(v, y, z){
v = 1;
y = 'hello';
z = 'world';
var args = rxArgs.exec(arguments.callee.toString())[1].split(", ");
var i = args.length, values = {};
while (i--) values[args[i]] = eval(args[i]);
func(c('{v}. {y} {z}', values));
}
function c(template, values) {
return template.replace(/{(.*?)}/g, function(match) {
return values[match[1]];
});
}
At this point, however, you're introducing so much boilerplate into each function that you're probably better off simply inlining c()
instead.
function a(){
var v = 9;
var w = 2;
var x = 7;
func('{w} + {x} = {v}'.replace(/{(.*?)}/g, function(match) {
return eval(match[1]);
}));
}
function b(){
var v = 1;
var y = 'hello';
var z = 'world';
func('{v}. {y} {z}'.replace(/{(.*?)}/g, function(match) {
return eval(match[1]);
}));
}
I would strongly suggest having your code really parse the templates and interpret the "{foo}" references explicitly in your own code, instead of using eval()
for everything.
It's not really clear why code like your "a()" and "b()" examples even need a template mechanism. In a language with first-class function objects like Javascript, what your code seems suspiciously desirous of achieving can be done much better by just programming functionally.
EDIT:
Your question seems to imply that the values will be coming from the caller. If they're all coming that way, you could just pass along the arguments
object to c
.
Then in the c
function, grab the next item in the arguments
object you passed for each match in the template
.
Example: http://jsfiddle.net/u99Bj/1/
function a(){
var template = '{w} + {x} = {v}';
c(template,arguments);
}
function b(){
var template = '{v}. {y} {z}';
c(template,arguments);
}
function c( template, args ){
var re = /{(.+?)}/;
var i = 0;
var match = template.match(re);
while (match != null){
template = template.replace(re, args[i++]);
match = template.match(re);
}
alert(template);
}
If some of the functions will have some static values, then you would need to convert the arguments
into an Array
, and supplement the Array
as needed.
Can't you do that :
function c(template,caller)
{ ...
}
and call
c(template,this)
then you could just get the variables as members of this (passing the function as an object instead of passing its scope)
EDIT
What about this approach?
function a(){
this.v = 9;
this.w = 2;
this.x = 7;
this.template = '{w} + {x} = {v}';
}
function c(obj){
var template = obj.template;
var re = /{(.+?)}/;
var match = template.match(re);
while (match != null){
template = template.replace(re, obj[match[1]]);
match = template.match(re);
}
alert(template);
}
<input type="button" value="a" onclick="c(new a())"/><br/>
What about this, using simply this keyword
<script type="text/javascript">
function a(){
this.v = 9;
this.w = 2;
this.x = 7;
var template = '{w} + {x} = {v}';
c(template);
}
function b(){
this.v = 1;
this.y = 'hello';
this.z = 'world';
var template = '{v}. {y} {z}';
c(template);
}
function c(template){
var re = /{(.+?)}/;
var match = template.match(re);
while (match != null){
template = template.replace(re, eval(match[1]));
match = template.match(re);
}
alert(template);
}
</script>
All variables will be initialized in document by default. You can encapsulate in other objects. Be careful about optional variables, as they may not be cleared on method call, and may interfere with parsing.
精彩评论