有没有更好的方法将一个函数的所有范围变量都放到另一个函数中?

时间:2011-01-20 17:53:54

标签: javascript

请考虑以下代码:

<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>

此代码有两个函数( a b )和一个解析函数 c ,它接收字符串模板作为参数并对其进行解析,使用调用函数范围内的变量( a b )。

这意味着函数 c 必须“知道”所有调用它的函数所知的变量。

我想要的是 c “知道”其来电者范围内的所有变数。

我的解决方案是 a b 中的这一行代码:

var func = eval('(' + c.toString() + ')');

这样做是将 c 重新定义为调用函数中的 func ,因此实际上使其成为调用者的子函数,从而将其置于同一范围内。

这个解决方案效果很好,但问题在于它很难看。我必须将 c 转换为字符串,并在每次要使用它时将其重新评估为函数。我希望有人能提出更好的解决方案,如果存在的话。

我不想将所有变量作为参数传递给 c ,因为:

  1. 要解析的模板可能非常大,包含从1到数十个变量的任何位置。
  2. 如果我将所有变量作为参数传递给 c 并使用 c 中的arguments数组访问它们,则意味着我必须在模板中使用数组表示法,这是不好的出于显而易见的原因进行练习。
  3. 将所有变量放入哈希映射对象并将该对象作为参数传递给 c 是可能的,但是在调用之前从调用者的变量创建这个哈希映射会产生巨大的编码开销。 C 即可。
  4. 注意: 请不要再向我指出解析功能并不完美,它只是我实际代码的一个简化示例。

6 个答案:

答案 0 :(得分:1)

你的事情太复杂了。您可以通过将替换值打包为对象而不是单个变量来消除跨范围的需要,并且使用g标志和替换函数可以大大简化c()。试一试:

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]];
    });
}

答案 1 :(得分:1)

玩了一段时间之后,这是我能够将本地范围传递到另一个函数的最接近的。这是非常hacky,涉及相当多的代码重复,仍然需要eval()(虽然没有那么多),但它可能是你正在寻找的。

基本上,这涉及将所有局部变量声明为函数参数(而不是使用var语句),以便通过.toString()将函数转换回源来提取它们的名称。调用a()b()时,这些参数提供!

(请注意,此处的c()功能与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]];
    });
}

然而,在这一点上,您在每个函数中引入了如此多的样板,而您最好只是简单地内联c()

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]);
    }));
}

答案 2 :(得分:0)

修改

您的问题似乎暗示价值将来自来电者。如果他们都是这样,你可以将arguments对象传递给c

然后在c函数中,抓取arguments中为每个匹配项传递的template对象中的下一项。

示例: 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);
}

如果某些功能会有一些静态值,那么您需要将arguments转换为Array,并根据需要补充Array

答案 3 :(得分:0)

我强烈建议您让代码真正解析模板并在您自己的代码中明确地解释“{foo}”引用,而不是使用eval()来处理所有内容。

为什么代码像“a()”和“b()”示例甚至需要模板机制一样,这并不是很清楚。在使用像Javascript这样的一流函数对象的语言中,只需编程功能就可以更好地完成你的代码似乎对实现的渴望。

答案 4 :(得分:0)

你能不能那样做:

function c(template,caller)
{ ... 
}

并致电

c(template,this)

然后您可以将变量作为成员(将函数作为对象传递而不是传递其范围)

修改

这种做法怎么样?

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/>

答案 5 :(得分:0)

如何使用此关键字

<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>

默认情况下,所有变量都将在文档中初始化。您可以封装在其他对象中。请注意可选变量,因为它们可能无法在方法调用中清除,并且可能会干扰解析。