JavaScript作用域——包括不使用传递或创建全局变量

3
我正在编写一些脚本,这些脚本包含一系列函数,这些函数都从一个调用中运行,并需要大量参数才能返回一个值。主要函数需要使用11个其他函数,这些函数需要使用相同的参数。我已经将其结构化如下:
function mainfunction(param1, param2, ..., param16)
{
    //do a bunch of stuff with the parameters
    return output;
}

function secondaryfunction1()
{
    //gets called by mainfunction
    //does a bunch of stuff with the parameters from mainfunction
}

有没有办法使传递给主函数的参数在不传递它们或将它们变成全局变量的情况下对所有次要函数可用?如果没有,那没关系,我会将它们作为参数传递 - 我只是好奇是否可以更优雅地完成。


好问题。引起了一些健康的辩论。+1。 - pseudosavant
5个回答

8

您可以将secondaryfunction1的定义放在mainfunction内:

function mainfunction(param1, param2, ..., param16){
    function secondaryfunction1() {
     // use param1, param2, ..., param16
    }
    secondaryfunction1();
}

更新:

正如@dystroy所指出的,如果您不需要在其他地方调用secondaryfunction1,那么这是可行的。在这种情况下,参数列表将来自哪里 - 我不知道。


在这种情况下,拥有私有方法(在“main函数”内定义的函数)绝对是正确的方法。在JavaScript中,变量的作用域限定在函数内部,因此在“mainFunction”中定义的任何内容都将对所有私有方法可用。 - pseudosavant
如果他需要在其他地方使用该函数,我仍然可能会遵循这种模式,并使用bind创建一个外部函数的实例,该实例使用与mainFunction相同的this作用域。 - pseudosavant
@dystroy - 是的,你说得对。我应该提到它。我会添加的。 - Igor
@pseudosavant 我看不出与 @dystroy 建议的概念上的区别。我认为 bind 是将对象作为参数传递的一种不优雅的方式。 - 1983
只有在使用外部函数时,他才需要使用bind,这些函数旨在供应用程序的其他部分使用。如果所有使用的函数都需要相同的参数,那么创建一个具有公共方法的对象似乎是最合理的选择。这样,所有函数的状态都会“自动”保持一致。 - pseudosavant
在这种特殊情况下,确实没有在其他地方调用辅助函数,所以这是我将要处理的响应。根据您提供的结构,我想在这种情况下需要先定义辅助函数再调用它们,因为它们不能在解析时定义。 - Rstevoa

5
您可以使用argumentsmainFunction的所有参数传递给secondaryFunction1。但那样做是很愚蠢的。
您应该做的,而且通常所做的,是嵌入一个“选项”对象中的所有参数:
function mainfunction(options){
    secondaryfunction1(options);
}

function secondaryfunction1(options) {
     // use options.param1, etc.
}

// let's call it
mainfunction({param1: 0, param2: "yes?"});

这带来了其他的优点,比如:

  • 命名传递的参数,而不是通过计算参数数量来确定要更改的参数。没有一个理智的库会让你将16个参数作为无名直接参数传递给函数。
  • 使您能够只传递需要的参数(其他参数默认)。

2
@pseudosavant 这是通常且正确的解决方案。例如,在所有需要大量参数的jQuery函数中,您都可以找到它。 - Denys Séguret
@svidgen 我只是举了一个例子。你知道有哪个库可以让你直接传递16个参数作为参数吗(YourCorporateHell库不算)? - Denys Séguret
1
并不是说我不同意你的解决方案。我确实同意使用参数对象比实际参数列表几乎总是更好。 - svidgen
@dystroy 为了保护 ps,Java 经常使用那种风格(或者建造者)。我认为这是因为 Java 缺乏一个<strike>好的</strike>合理的对象字面量语法。 - John Dvorak
@JanDvorak 在Java中,您也会使用对象,无论是作为参数还是函数的接收者。您永远不会使用超过4或5个参数调用函数,因为您无法维护它们。 - Denys Séguret
显示剩余7条评论

0

@Igor的答案(或某种变体)是正确的方法。但是,如果您必须在其他地方使用这些函数(如@dystroy所指出的),则还有另一种可能性。将参数组合成一个对象,并将该对象传递给辅助函数。

function combineEm() {
    // Get all parameters into an array.
    var args = [].slice.call(arguments, 0),
        output = {},
        i;
    // Now put them in an object
    for (i = 0; i < args.length; i++) {
        output["param" + i] = args[i];
    }
    return output;
}

从您的主函数中,您可以执行以下操作:

function mainfunction(param1, param2, ..., param16) {
    var params = combineEm(param1, param2, ..., param16);
    var output = secondaryfunction(params);
    // etc.
    return output;
}

无论是否使用对象,arguments,或者单独的参数,这并不是我认为的问题的核心所在。我认为更重要的是作用域以及如何在函数上下文中访问和传递变量。 - pseudosavant
对我来说,他是在问如何避免向11个函数传递16个参数。这个答案要求你将这些参数传递给1个函数,然后再将对象传递给11个函数。所以它确实实现了他想要做的事情。无论如何,我同意Igor的答案更好,特别是关于作用域方面的。 - ahuth
我理解OP的问题是如何避免重复向11个函数传递16个参数。他已经知道如何将这些参数重复传递给一堆函数了。 - pseudosavant

0

编辑:我只想澄清到目前为止所有提出的建议都是可行的。它们各自有利弊。

我尝试了一些对其他答案的修改建议,但最终我觉得我需要发布我的解决方案。

var externalFn = function(options) {
  var str = options.str || 'hello world';

  alert(str);
};

var main = function(options) {
  var privateMethod = function() {
    var str = options.str || "foobar";

    alert("str: " + str);
  };

  // Bind a private version of an external function
        var privateMethodFromExternal = externalFn.bind(this, options);

  privateMethod();
  privateMethodFromExternal();
};

main({ str: "abc123"});
// alerts 'str: abc123'
// alerts 'abc123'
main({});
// alerts 'str: foobar'
// alerts 'hello world'
  • 问题的主要点似乎是“主函数”使用的函数不应该一直需要传递选项/上下文。
  • 此示例显示了如何在函数内部使用privateMethods
  • 它还展示了如何获取外部函数(您可能在main之外使用它们)并绑定其私有方法版本以供在main中使用。
  • 我更喜欢使用某种“选项”对象,但这方面对于OP真正询问的作用域问题并不重要。您也可以使用“常规”参数。

此示例可在codepen上找到


没有评论就踩我?那我怎么知道我的解决方案哪里错了呢? :P - pseudosavant
+1 为另一个具有可行答案的视角。这是否适用于16个参数和11个函数?此外,我不想代表 OP 说话,但这可能对他想要实现的目标来说太高级了(对我来说肯定是太高级了)。 - ahuth

0

如果你对这种事情感兴趣,这里有一个非常调皮的解决方案。

var f1 = function() {
  var a = 1;
  var _f2 = f2.toString().replace(/^function[^{}]+{/, '');
  _f2 = _f2.substr(0, _f2.length - 2);
  eval(_f2);
}

var f2 = function(a) {
  var a = a || 0;
  console.log(a);
}

f2();   // logs 0
f1();   // logs 1

它在当前作用域中完全执行某些外部函数的内容。

然而,这种诡计几乎肯定表明您的项目组织不当。调用外部函数通常不应该比传递对象更困难,正如dystroy的答案所建议的那样,在作用域内定义函数,就像Igor的答案所建议的那样,或者通过将一些外部函数附加到this并主要针对this的属性编写函数。就像这样:

var FunLib = {
  a : 0,
  do : function() {
    console.log(this.a);
  }
}

var Class = function() {
  this.a = 1;
  this.do = FunLib.do;
  this.somethingThatDependsOnDo = function() {
    this.a++;
    this.do();
  }
}

var o = new Class();

FunLib.do()                     // 0
o.do()                          // 1
o.somethingThatDependsOnDo();   // 2
o.do()                          // 2 now

同样,可能更好地解决方案是使用类层次结构。

function BasicShoe {
  this.steps_taken = 0;
  this.max_steps = 100000;

  this.doStep = function() {
    this.steps_taken++;
    if (this.steps_taken > this.max_steps) {
      throw new Exception("Broken Shoe!");
    }
  }
}

function Boot {
  this.max_steps = 150000;
  this.kick_step_equivalent = 10;

  this.doKick = function() {
    for (var i = 0; i < this.kick_step_equivalent; i++) {
      this.doStep();
    }
  }
}
Boot.prototype = new BasicShoe();

function SteelTippedBoot {
  this.max_steps = 175000;
  this.kick_step_equivalent = 0;
}
SteelTippedBoot.prototype = new Boot();

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接